From a213ec28dc59ecb75cae46bf3c31de2f664cd3f1 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 6 Jan 2025 10:42:28 -0500 Subject: [PATCH 01/63] thread context fetching --- src/coreclr/debug/daccess/cdac.cpp | 12 ++++++- .../Contracts/IThread.cs | 1 + .../Target.cs | 2 ++ .../Contracts/Thread_1.cs | 36 +++++++++++++++++++ .../ContractDescriptorTarget.cs | 15 +++++++- .../managed/cdacreader/inc/cdac_reader.h | 8 ++++- .../managed/cdacreader/src/Entrypoints.cs | 21 +++++++++-- .../cdacreader/src/Legacy/SOSDacImpl.cs | 10 ++++++ .../ContractDescriptorBuilder.cs | 2 +- 9 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index cf129318ae9618..66277f958f66e5 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -41,6 +41,16 @@ namespace return S_OK; } + + int ReadThreadContext(uint32_t threadId, uint32_t contextFlags, uint32_t contextBufferSize, uint8_t* contextBuffer, void* context) + { + ICorDebugDataTarget* target = reinterpret_cast(context); + HRESULT hr = target->GetThreadContext(threadId, contextFlags, contextBufferSize, contextBuffer); + if (FAILED(hr)) + return hr; + + return S_OK; + } } CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl) @@ -53,7 +63,7 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown _ASSERTE(init != nullptr); intptr_t handle; - if (init(descriptorAddr, &ReadFromTargetCallback, target, &handle) != 0) + if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, target, &handle) != 0) { ::FreeLibrary(cdacLib); return {}; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index cca1c522f10174..c37209226cb376 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -48,6 +48,7 @@ internal interface IThread : IContract public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); + public virtual int GetThreadContext(TargetPointer thread) => throw new NotImplementedException(); } internal readonly struct Thread : IThread diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index aa1174cd0b2e16..bef88a93f361fe 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -27,6 +27,8 @@ internal abstract class Target /// public abstract bool IsLittleEndian { get; } + public abstract int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); + /// /// Reads a well-known global pointer value from the target process /// diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 796eb89dffce71..08b477c45ccbc5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -79,4 +79,40 @@ private TargetPointer GetThreadFromLink(TargetPointer threadLink) // Get the address of the thread containing the link return new TargetPointer(threadLink - _threadLinkOffset); } + + [Flags] + public enum AMD64ContextFlags : uint + { + CONTEXT_AMD = 0x00100000, + CONTEXT_CONTROL = CONTEXT_AMD | 0x00000001, + CONTEXT_INTEGER = CONTEXT_AMD | 0x00000002, + CONTEXT_SEGMENTS = CONTEXT_AMD | 0x00000004, + CONTEXT_FLOATING_POINT = CONTEXT_AMD | 0x00000008, + CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD | 0x00000010, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, + CONTEXT_XSTATE = CONTEXT_AMD | 0x00000040, + CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x00000080, + } + + int IThread.GetThreadContext(TargetPointer threadPointer) + { + ThreadData threadData = ((IThread)this).GetThreadData(threadPointer); + int hr; + unsafe + { + byte[] bytes = new byte[0x700]; + + fixed (byte* ptr = bytes) + { + Span buffer = bytes; + hr = _target.GetThreadContext((uint)threadData.OSId.Value, (uint)AMD64ContextFlags.CONTEXT_FULL, 0x700, buffer); + + Console.WriteLine(); + } + } + + + return hr; + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 668c9c988d1d4d..700047ebd37917 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -43,13 +43,17 @@ private readonly struct Configuration public override DataCache ProcessedData { get; } public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); + public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); - public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, out ContractDescriptorTarget? target) + public GetTargetThreadContextDelegate? getThreadContext; + + public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, out ContractDescriptorTarget? target) { Reader reader = new Reader(readFromTarget); if (TryReadContractDescriptor(contractDescriptor, reader, out Configuration config, out ContractDescriptorParser.ContractDescriptor? descriptor, out TargetPointer[] pointerData)) { target = new ContractDescriptorTarget(config, descriptor!, pointerData, reader); + target.getThreadContext = getThreadContext; return true; } @@ -213,6 +217,15 @@ private static DataType GetDataType(string type) public override int PointerSize => _config.PointerSize; public override bool IsLittleEndian => _config.IsLittleEndian; + public override int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill) + { + if (getThreadContext is null) + throw new InvalidOperationException("GetThreadContext is not available"); + + int hr = getThreadContext(threadId, contextFlags, contextSize, bufferToFill); + return hr; + } + /// /// Read a value from the target in target endianness /// diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 8fe07b4d013e46..6597a9dbd70178 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -12,9 +12,15 @@ extern "C" // Initialize the cDAC reader // descriptor: the address of the descriptor in the target process // read_from_target: a callback that reads memory from the target process +// read_thread_context: a callback that reads the context of a thread in the target process // read_context: a context pointer that will be passed to read_from_target // handle: returned opaque the handle to the reader. This should be passed to other functions in this API. -int cdac_reader_init(uint64_t descriptor, int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), void* read_context, /*out*/ intptr_t* handle); +int cdac_reader_init( + uint64_t descriptor, + int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), + int(*read_thread_context)(uint32_t, uint32_t, uint32_t, uint8_t*, void*), + void* read_context, + /*out*/ intptr_t* handle); // Free the cDAC reader // handle: handle to the reader diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index cc24447ef3f133..e2df262d9b1993 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -12,16 +12,31 @@ internal static class Entrypoints private const string CDAC = "cdac_reader_"; [UnmanagedCallersOnly(EntryPoint = $"{CDAC}init")] - private static unsafe int Init(ulong descriptor, delegate* unmanaged readFromTarget, void* readContext, IntPtr* handle) + private static unsafe int Init( + ulong descriptor, + delegate* unmanaged readFromTarget, + delegate* unmanaged readThreadContext, + void* readContext, + IntPtr* handle) { // TODO: [cdac] Better error code/details - if (!ContractDescriptorTarget.TryCreate(descriptor, (address, buffer) => + if (!ContractDescriptorTarget.TryCreate( + descriptor, + (address, buffer) => { fixed (byte* bufferPtr = buffer) { return readFromTarget(address, bufferPtr, (uint)buffer.Length, readContext); } - }, out ContractDescriptorTarget? target)) + }, + (threadId, contextFlags, contextSize, buffer) => + { + fixed (byte* bufferPtr = buffer) + { + return readThreadContext(threadId, contextFlags, contextSize, bufferPtr, readContext); + } + }, + out ContractDescriptorTarget? target)) return -1; GCHandle gcHandle = GCHandle.Alloc(target); diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e4367f004b0d91..bda1c27a37fc67 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -195,6 +195,16 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes return HResults.E_INVALIDARG; } + try + { + ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); + _target.Contracts.Thread.GetThreadContext(tsdata.FirstThread); + } + catch + { + Console.WriteLine("uh oh"); + } + int hr = HResults.S_OK; try { diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index e22d8653368ebf..4c9fdef137037f 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -168,6 +168,6 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta throw new InvalidOperationException("Context already created"); ulong contractDescriptorAddress = CreateDescriptorFragments(); MockMemorySpace.ReadContext context = GetReadContext(); - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, out target); + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, out target); } } From 8f2b7e75f4b610979a0279c3cee07813bca2deb0 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 7 Jan 2025 19:56:51 -0500 Subject: [PATCH 02/63] iterate and debug print Frames --- .../debug/runtimeinfo/datadescriptor.h | 46 ++++++++++++ src/coreclr/vm/amd64/gmscpu.h | 12 ++++ src/coreclr/vm/frames.h | 57 +++++++++++++++ .../DataType.cs | 12 ++++ .../Contracts/StackWalking/FrameIterator.cs | 70 +++++++++++++++++++ .../Contracts/Thread_1.cs | 12 ++++ .../Data/Frames/Frame.cs | 50 +++++++++++++ .../Data/Frames/HelperMethodFrame.cs | 53 ++++++++++++++ .../Data/Frames/InlinedCallFrame.cs | 22 ++++++ .../Data/Frames/LazyMachState.cs | 25 +++++++ .../cdacreader/tests/TestPlaceholderTarget.cs | 5 ++ 11 files changed, 364 insertions(+) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index e58d91bd6cab3a..529d9698638100 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -570,6 +570,44 @@ CDAC_TYPE_FIELD(GCCoverageInfo, /*pointer*/, SavedCode, offsetof(GCCoverageInfo, CDAC_TYPE_END(GCCoverageInfo) #endif // HAVE_GCCOVER +CDAC_TYPE_BEGIN(Frame) +CDAC_TYPE_INDETERMINATE(Frame) +CDAC_TYPE_FIELD(Frame, /*pointer*/, Next, cdac_data::Next) +CDAC_TYPE_END(Frame) + +CDAC_TYPE_BEGIN(InlinedCallFrame) +CDAC_TYPE_SIZE(sizeof(InlinedCallFrame)) +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallSiteSP, offsetof(InlinedCallFrame, m_pCallSiteSP)) +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallerReturnAddress, offsetof(InlinedCallFrame, m_pCallerReturnAddress)) +CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CalleeSavedFP, offsetof(InlinedCallFrame, m_pCalleeSavedFP)) +CDAC_TYPE_END(InlinedCallFrame) + +#define DEFINE_FRAME_TYPE(frameType) \ + CDAC_TYPE_BEGIN(frameType) \ + CDAC_TYPE_SIZE(sizeof(frameType)) \ + CDAC_TYPE_FIELD(frameType, /*uint32*/, FrameAttributes, cdac_data::FrameAttributes) \ + CDAC_TYPE_FIELD(frameType, /*pointer*/, FCallEntry, cdac_data::FCallEntry) \ + CDAC_TYPE_FIELD(frameType, /*LazyMachState*/, LazyMachState, cdac_data::LazyMachState) \ + CDAC_TYPE_END(frameType) + +DEFINE_FRAME_TYPE(HelperMethodFrame) +DEFINE_FRAME_TYPE(HelperMethodFrame_1OBJ) +DEFINE_FRAME_TYPE(HelperMethodFrame_2OBJ) +DEFINE_FRAME_TYPE(HelperMethodFrame_3OBJ) +DEFINE_FRAME_TYPE(HelperMethodFrame_PROTECTOBJ) +#undef DEFINE_FRAME_TYPE + +CDAC_TYPE_BEGIN(LazyMachState) +CDAC_TYPE_SIZE(sizeof(LazyMachState)) +CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, InstructionPointer, cdac_data::InstructionPointer) +CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, StackPointer, cdac_data::StackPointer) +CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, CapturedInstructionPointer, offsetof(LazyMachState, m_CaptureRip)) +CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, CapturedStackPointer, offsetof(LazyMachState, m_CaptureRsp)) +CDAC_TYPE_FIELD(LazyMachState, /*CalleeSavedRegisters*/, CalleeSavedRegisters, cdac_data::CalleeSavedRegisters) +CDAC_TYPE_FIELD(LazyMachState, /*CalleeSavedRegistersPointers*/, CalleeSavedRegistersPointers, cdac_data::CalleeSavedRegistersPointers) +CDAC_TYPE_END(LazyMachState) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -577,6 +615,14 @@ CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) + +// Add VPtr for all defined Frame types. Used to differentiate Frame objects. +#define FRAME_TYPE_NAME(frameType) \ + CDAC_GLOBAL_POINTER(frameType##VPtr, frameType::GetMethodFrameVPtr()) + + #include "frames.h" +#undef FRAME_TYPE_NAME + CDAC_GLOBAL(MethodDescTokenRemainderBitCount, uint8, METHOD_TOKEN_REMAINDER_BIT_COUNT) #if FEATURE_EH_FUNCLETS CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) diff --git a/src/coreclr/vm/amd64/gmscpu.h b/src/coreclr/vm/amd64/gmscpu.h index 411f1cf0c71b88..13036051767ac4 100644 --- a/src/coreclr/vm/amd64/gmscpu.h +++ b/src/coreclr/vm/amd64/gmscpu.h @@ -115,6 +115,18 @@ struct LazyMachState : public MachState // ULONG64 m_CaptureRip; ULONG64 m_CaptureRsp; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t InstructionPointer = offsetof(LazyMachState, m_Rip); + static constexpr size_t StackPointer = offsetof(LazyMachState, m_Rsp); + static constexpr size_t ReturnAddress = offsetof(LazyMachState, _pRetAddr); + static constexpr size_t CalleeSavedRegisters = offsetof(LazyMachState, m_Capture); + static constexpr size_t CalleeSavedRegistersPointers = offsetof(LazyMachState, m_Ptrs); }; inline void LazyMachState::setLazyStateFromUnwind(MachState* copy) diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 4ea6e33341d0a3..7312cf69ed9484 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -766,8 +766,15 @@ class Frame : public FrameBase void PopIfChained(); #endif // TARGET_UNIX && !DACCESS_COMPILE + + friend struct ::cdac_data; }; +template<> +struct cdac_data +{ + static constexpr size_t Next = offsetof(Frame, m_Next); +}; //----------------------------------------------------------------------------- // This frame provides a context for a code location at which @@ -1447,6 +1454,16 @@ class HelperMethodFrame : public Frame // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame) + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame, m_Attribs); + static constexpr size_t FCallEntry = offsetof(HelperMethodFrame, m_FCallEntry); + static constexpr size_t LazyMachState = offsetof(HelperMethodFrame, m_MachState); }; // Restores registers saved in m_MachState @@ -1532,6 +1549,16 @@ class HelperMethodFrame_1OBJ : public HelperMethodFrame // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_1OBJ) + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_1OBJ, m_Attribs); + static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_1OBJ, m_FCallEntry); + static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_1OBJ, m_MachState); }; @@ -1593,6 +1620,16 @@ class HelperMethodFrame_2OBJ : public HelperMethodFrame // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_2OBJ) + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_2OBJ, m_Attribs); + static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_2OBJ, m_FCallEntry); + static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_2OBJ, m_MachState); }; //----------------------------------------------------------------------------- @@ -1659,6 +1696,16 @@ class HelperMethodFrame_3OBJ : public HelperMethodFrame // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_3OBJ) + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_3OBJ, m_Attribs); + static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_3OBJ, m_FCallEntry); + static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_3OBJ, m_MachState); }; @@ -1725,6 +1772,16 @@ class HelperMethodFrame_PROTECTOBJ : public HelperMethodFrame // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_PROTECTOBJ) + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_PROTECTOBJ, m_Attribs); + static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_PROTECTOBJ, m_FCallEntry); + static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_PROTECTOBJ, m_MachState); }; class FramedMethodFrame : public TransitionFrame 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 8ea30a324bfaee..6e65a763e05a1a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -82,4 +82,16 @@ public enum DataType MethodImpl, NativeCodeSlot, GCCoverageInfo, + + Frame, + InlinedCallFrame, + HelperMethodFrame, + HelperMethodFrame_1OBJ, + HelperMethodFrame_2OBJ, + HelperMethodFrame_3OBJ, + HelperMethodFrame_PROTECTOBJ, + DebuggerU2MCatchHandlerFrame, + DynamicHelperFrame, + + LazyMachState, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs new file mode 100644 index 00000000000000..1fadd69ab979fe --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs @@ -0,0 +1,70 @@ +// 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.IO; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class FrameIterator +{ + public static void EnumerateFrames(Target target, TargetPointer framePointer) + { + string outputhPath = "C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt"; + using StreamWriter writer = new StreamWriter(outputhPath); + Console.SetOut(writer); + + TargetPointer terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); + + while (framePointer != terminator) + { + Data.Frame frame = target.ProcessedData.GetOrAdd(framePointer); + HandleFrame(target, framePointer); + framePointer = frame.Next; + } + + writer.Flush(); + } + + public static void HandleFrame(Target target, TargetPointer framePointer) + { + Data.Frame frame = target.ProcessedData.GetOrAdd(framePointer); + switch (frame.Type) + { + case DataType.InlinedCallFrame: + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(framePointer); + Print(inlinedCallFrame); + break; + case DataType.HelperMethodFrame: + case DataType.HelperMethodFrame_1OBJ: + case DataType.HelperMethodFrame_2OBJ: + case DataType.HelperMethodFrame_3OBJ: + case DataType.HelperMethodFrame_PROTECTOBJ: + Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(framePointer); + Print(helperMethodFrame); + break; + default: + Console.WriteLine($"Unknown frame type: {frame.Type}"); + break; + } + } + + public static void Print(InlinedCallFrame inlinedCallFrame) + { + Console.WriteLine($"[InlinedCallFrame: IP={inlinedCallFrame.CallerReturnAddress}, SP={inlinedCallFrame.CallSiteSP}]"); + } + + public static void Print(HelperMethodFrame helperMethodFrame) + { + bool isValid = helperMethodFrame.LazyMachState.ReturnAddress != 0; + if (isValid) + { + Console.WriteLine($"[{helperMethodFrame.Type}: IP={helperMethodFrame.LazyMachState.InstructionPointer}, SP={helperMethodFrame.LazyMachState.StackPointer}, RA={helperMethodFrame.LazyMachState.ReturnAddress}]"); + } + else + { + Console.WriteLine($"[{helperMethodFrame.Type}: IP={helperMethodFrame.LazyMachState.InstructionPointer}, Invalid LazyMachState]"); + } + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 08b477c45ccbc5..29779a7216c205 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -99,6 +100,17 @@ int IThread.GetThreadContext(TargetPointer threadPointer) { ThreadData threadData = ((IThread)this).GetThreadData(threadPointer); int hr; + + FrameIterator.EnumerateFrames(_target, threadData.Frame); + + TargetPointer framePointer = threadData.Frame; + while (framePointer != new TargetPointer(ulong.MaxValue)) + { + Data.Frame frame = _target.ProcessedData.GetOrAdd(framePointer); + Console.WriteLine(frame.Type); + framePointer = frame.Next; + } + unsafe { byte[] bytes = new byte[0x700]; 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 new file mode 100644 index 00000000000000..d137b737112db9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs @@ -0,0 +1,50 @@ +// 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 +{ + private static readonly List SupportedFrameTypes = [ + "InlinedCallFrame", + "HelperMethodFrame", + "HelperMethodFrame_1OBJ", + "HelperMethodFrame_2OBJ", + "HelperMethodFrame_3OBJ", + "HelperMethodFrame_PROTECTOBJ", + "DebuggerU2MCatchHandlerFrame", + "DynamicHelperFrame", + ]; + + static Frame IData.Create(Target target, TargetPointer address) + => new Frame(target, address); + + public Frame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Frame); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + Type = FindType(target, address); + } + + private static DataType FindType(Target target, TargetPointer address) + { + TargetPointer instanceVptr = target.ReadPointer(address); + + foreach (string frameTypeName in SupportedFrameTypes) + { + TargetPointer typeVptr = target.ReadGlobalPointer(frameTypeName + "VPtr"); + if (instanceVptr == typeVptr) + { + return Enum.TryParse(frameTypeName, out DataType type) ? type : DataType.Unknown; + } + } + + return DataType.Unknown; + } + + public TargetPointer Next { get; init; } + public DataType Type { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs new file mode 100644 index 00000000000000..22e1d8d433d2f2 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs @@ -0,0 +1,53 @@ +// 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; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class HelperMethodFrame : IData +{ + private static readonly List HelperMethodFrameTypes = [ + "HelperMethodFrame", + "HelperMethodFrame_1OBJ", + "HelperMethodFrame_2OBJ", + "HelperMethodFrame_3OBJ", + "HelperMethodFrame_PROTECTOBJ", + ]; + + private static DataType FindType(Target target, TargetPointer address) + { + TargetPointer instanceVptr = target.ReadPointer(address); + + foreach (string frameTypeName in HelperMethodFrameTypes) + { + TargetPointer typeVptr = target.ReadGlobalPointer(frameTypeName + "VPtr"); + if (instanceVptr == typeVptr) + { + return Enum.TryParse(frameTypeName, out DataType type) ? type : DataType.Unknown; + } + } + + return DataType.Unknown; + } + + static HelperMethodFrame IData.Create(Target target, TargetPointer address) + => new HelperMethodFrame(target, address); + + public HelperMethodFrame(Target target, TargetPointer address) + { + Type = FindType(target, address); + Debug.Assert(Type != DataType.Unknown); + Target.TypeInfo type = target.GetTypeInfo(Type); + FrameAttributes = target.Read(address + (ulong)type.Fields[nameof(FrameAttributes)].Offset); + FCallEntry = target.ReadPointer(address + (ulong)type.Fields[nameof(FCallEntry)].Offset); + LazyMachState = target.ProcessedData.GetOrAdd(address + (ulong)type.Fields[nameof(LazyMachState)].Offset); + } + + public DataType Type { get; } + public uint FrameAttributes { get; } + public TargetPointer FCallEntry { get; } + public LazyMachState LazyMachState { 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 new file mode 100644 index 00000000000000..0b2cce103cf6a1 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.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 InlinedCallFrame : IData +{ + static InlinedCallFrame IData.Create(Target target, TargetPointer address) + => new InlinedCallFrame(target, address); + + public InlinedCallFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.InlinedCallFrame); + CallSiteSP = target.ReadPointer(address + (ulong)type.Fields[nameof(CallSiteSP)].Offset); + CallerReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(CallerReturnAddress)].Offset); + CalleeSavedFP = target.ReadPointer(address + (ulong)type.Fields[nameof(CalleeSavedFP)].Offset); + } + + 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/LazyMachState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs new file mode 100644 index 00000000000000..90b58ab0e6fb69 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.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. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class LazyMachState : IData +{ + static LazyMachState IData.Create(Target target, TargetPointer address) + => new LazyMachState(target, address); + + public LazyMachState(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.LazyMachState); + InstructionPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(InstructionPointer)].Offset); + StackPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(StackPointer)].Offset); + ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + } + + public TargetPointer InstructionPointer { get; } + public TargetPointer StackPointer { get; } + public TargetPointer ReturnAddress { get; } +} diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 007574ca7d7431..10e977d34e8d15 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -226,6 +226,11 @@ public override Target.TypeInfo GetTypeInfo(DataType dataType) throw new NotImplementedException(); } + public override int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill) + { + throw new InvalidOperationException("GetThreadContext is not available"); + } + public override Target.IDataCache ProcessedData => _dataCache; public override ContractRegistry Contracts => _contractRegistry; From 6f2f4ab76dde2a9b15b72051e3c8a9e77cbbb0e3 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 15 Jan 2025 19:24:59 -0500 Subject: [PATCH 03/63] add more functionality needed for stack walking --- src/coreclr/debug/runtimeinfo/contracts.jsonc | 1 + .../debug/runtimeinfo/datadescriptor.h | 5 + .../ContractRegistry.cs | 4 + .../Contracts/IExecutionManager.cs | 2 + .../Contracts/IStackWalk.cs | 18 ++ .../Contracts/IThread.cs | 1 - .../DataType.cs | 27 +- .../ExecutionManagerBase.EEJitManager.cs | 89 ++++++- ...ecutionManagerBase.ReadyToRunJitManager.cs | 2 + .../ExecutionManager/ExecutionManagerBase.cs | 27 ++ .../Contracts/StackWalk/AMD64Context.cs | 246 ++++++++++++++++++ .../FrameIterator.cs | 47 +++- .../Contracts/StackWalk/IContext.cs | 17 ++ .../Contracts/StackWalk/RegisterAttribute.cs | 41 +++ .../Contracts/StackWalk/StackWalkFactory.cs | 18 ++ .../Contracts/StackWalk/StackWalk_1.cs | 91 +++++++ .../Contracts/Thread_1.cs | 47 ---- .../Data/Frames/Frame.cs | 60 ++++- .../Data/RealCodeHeader.cs | 8 + .../Data/RuntimeFunction.cs | 2 +- .../CachingContractRegistry.cs | 2 + .../cdacreader/src/Legacy/SOSDacImpl.cs | 23 +- 22 files changed, 680 insertions(+), 98 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/{StackWalking => StackWalk}/FrameIterator.cs (54%) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/RegisterAttribute.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index ae8cb684ab2c72..b98f56c9b5583c 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -20,5 +20,6 @@ "PrecodeStubs": 1, "ReJIT": 1, "RuntimeTypeSystem": 1, + "StackWalk": 1, "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 529d9698638100..18d3aa08bc4f1c 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -521,8 +521,13 @@ CDAC_TYPE_END(RangeSection) CDAC_TYPE_BEGIN(RealCodeHeader) CDAC_TYPE_INDETERMINATE(RealCodeHeader) CDAC_TYPE_FIELD(RealCodeHeader, /*pointer*/, MethodDesc, offsetof(RealCodeHeader, phdrMDesc)) +#ifdef FEATURE_EH_FUNCLETS +CDAC_TYPE_FIELD(RealCodeHeader, /*uint32*/, NumUnwindInfos, offsetof(RealCodeHeader, nUnwindInfos)) +CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof(RealCodeHeader, unwindInfos)) +#endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(RealCodeHeader) + CDAC_TYPE_BEGIN(CodeHeapListNode) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, Next, offsetof(HeapList, hpNext)) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, StartAddress, offsetof(HeapList, startAddress)) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 901084cd2c7d5e..b383a50b0db622 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -59,4 +59,8 @@ internal abstract class ContractRegistry /// Gets an instance of the ReJIT contract for the target. /// public abstract IReJIT ReJIT { get; } + /// + /// Gets an instance of the StackWalk contract for the target. + /// + public abstract IStackWalk StackWalk { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs index 994dec03a67ab1..2c4edda245f84a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs @@ -17,6 +17,8 @@ internal interface IExecutionManager : IContract CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException(); TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer GetModuleBaseAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException(); + TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => throw new NotImplementedException(); } internal readonly struct ExecutionManager : IExecutionManager diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs new file mode 100644 index 00000000000000..6297c57802108a --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -0,0 +1,18 @@ +// 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; + +internal interface IStackWalk : IContract +{ + static string IContract.Name => nameof(StackWalk); + + public void TestEntry() => throw new NotImplementedException(); +} + +internal struct StackWalk : IStackWalk +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index c37209226cb376..cca1c522f10174 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -48,7 +48,6 @@ internal interface IThread : IContract public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); - public virtual int GetThreadContext(TargetPointer thread) => throw new NotImplementedException(); } internal readonly struct Thread : IThread 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 6e65a763e05a1a..05f6b0aa5ceedb 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -90,8 +90,33 @@ public enum DataType HelperMethodFrame_2OBJ, HelperMethodFrame_3OBJ, HelperMethodFrame_PROTECTOBJ, - DebuggerU2MCatchHandlerFrame, + + ResumableFrame, + RedirectedTHreadFrame, + FaultingExceptionFrame, + SoftwareExceptionFrame, + FuncEvalFrame, + UnmanagedToManagedFrame, + ComMethodFrame, + CLRToCOMMethodFrame, + ComPrestubMethodFrame, + PInvokeCalliFrame, + HijackFrame, + PrestubMethodFrame, + CallCountingHelperFrame, + StubDispatchFrame, + ExternalMethodFrame, DynamicHelperFrame, + InterpreterFrame, + ProtectByRefsFrame, + ProtectValueClassFrame, + DebuggerClassInitMarkFrame, + DebuggerSecurityCodeMarkFrame, + DebuggerExitFrame, + DebuggerU2MCatchHandlerFrame, + TailCallFrame, + ExceptionFilterFrame, + AssumeByrefFromJITStack, LazyMachState, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs index 4067c714c7eea5..7ae720c04e9ace 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs @@ -28,25 +28,63 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer if (rangeSection.Data == null) throw new ArgumentException(nameof(rangeSection)); - TargetPointer start = FindMethodCode(rangeSection, jittedCodeAddress); - if (start == TargetPointer.Null) + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) return false; - Debug.Assert(start.Value <= jittedCodeAddress.Value); - TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - start.Value); - // See EEJitManager::GetCodeHeaderFromStartAddress in vm/codeman.h - int codeHeaderOffset = Target.PointerSize; - TargetPointer codeHeaderIndirect = new TargetPointer(start - (ulong)codeHeaderOffset); - if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) - { + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + TargetNUInt relativeOffset = new TargetNUInt(jittedCodeAddress.Value - codeStart.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) return false; - } - TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); - Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); - info = new CodeBlock(start.Value, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); + + info = new CodeBlock(codeStart.Value, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager); return true; } + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPointer imageBase, TargetCodePointer jittedCodeAddress) + { + // TODO: This only works with funclets enabled. See runtime definition of RealCodeHeader for more info. + if (rangeSection.IsRangeList) + return TargetPointer.Null; + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + TargetPointer codeStart = FindMethodCode(rangeSection, jittedCodeAddress); + if (codeStart == TargetPointer.Null) + return TargetPointer.Null; + Debug.Assert(codeStart.Value <= jittedCodeAddress.Value); + + if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) + return TargetPointer.Null; + + if (Target.GetTypeInfo(DataType.RuntimeFunction).Size is not uint runtimeFunctionSize) + { + throw new InvalidOperationException("Unable to get RuntimeFunction size"); + } + if (realCodeHeader.NumUnwindInfos is not uint numUnwindInfos) + { + throw new InvalidOperationException("Unable to get NumUnwindInfos"); + } + if (realCodeHeader.UnwindInfos is not TargetPointer unwindInfos) + { + throw new InvalidOperationException("Unable to get NumUnwindInfos"); + } + + + for (ulong i = 0; i < numUnwindInfos; i++) + { + TargetPointer unwindInfoAddress = unwindInfos + (i * runtimeFunctionSize); + Data.RuntimeFunction runtimeFunction = Target.ProcessedData.GetOrAdd(unwindInfoAddress); + if (runtimeFunction.BeginAddress + imageBase <= jittedCodeAddress.Value && runtimeFunction.EndAddress + imageBase >= jittedCodeAddress.Value) + { + return unwindInfoAddress; + } + } + + return TargetPointer.Null; + } + private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // EEJitManager::FindMethodCode @@ -59,5 +97,30 @@ private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointe Data.CodeHeapListNode heapListNode = Target.ProcessedData.GetOrAdd(heapListAddress); return _nibbleMap.FindMethodCode(heapListNode, jittedCodeAddress); } + + private bool GetRealCodeHeader(RangeSection rangeSection, TargetPointer codeStart, [NotNullWhen(true)] out Data.RealCodeHeader? realCodeHeader) + { + realCodeHeader = null; + // EEJitManager::JitCodeToMethodInfo + if (rangeSection.IsRangeList) + return false; + + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + if (codeStart == TargetPointer.Null) + return false; + + // See EEJitManager::GetCodeHeaderFromStartAddress in vm/codeman.h + int codeHeaderOffset = Target.PointerSize; + TargetPointer codeHeaderIndirect = new TargetPointer(codeStart - (ulong)codeHeaderOffset); + if (RangeSection.IsStubCodeBlock(Target, codeHeaderIndirect)) + { + return false; + } + TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); + realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); + return true; + } } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index 8390469e1d76c5..3b70339243091f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -94,6 +94,8 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPointer imageBase, TargetCodePointer jittedCodeAddress) => ~0ul; + private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) { if (r2rInfo.DelayLoadMethodCallThunks == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs index e28c4b30071cf9..9ffff1a9b5672d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs @@ -65,6 +65,7 @@ protected JitManager(Target target) } public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); + public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPointer imageBase, TargetCodePointer jittedCodeAddress); } private sealed class RangeSection @@ -178,4 +179,30 @@ TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHand return info.StartAddress; } + + TargetPointer IExecutionManager.GetModuleBaseAddress(CodeBlockHandle codeInfoHandle) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, new TargetCodePointer(codeInfoHandle.Address)); + if (range.Data == null) + throw new InvalidOperationException($"{nameof(RangeSection)} not found for {codeInfoHandle.Address}"); + + return range.Data.RangeBegin; + } + + TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) + { + if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info)) + throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}"); + + RangeSection range = RangeSection.Find(_target, _topRangeSectionMap, _rangeSectionMapLookup, ip); + if (range.Data == null) + return TargetPointer.Null; + + JitManager jitManager = GetJitManager(range.Data); + + return jitManager.GetUnwindInfo(range, ((IExecutionManager)this).GetModuleBaseAddress(codeInfoHandle), ip); + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs new file mode 100644 index 00000000000000..7e32e936d973c7 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs @@ -0,0 +1,246 @@ +// 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.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +[StructLayout(LayoutKind.Explicit, Pack = 1)] +public struct AMD64Context : IContext +{ + [Flags] + public enum ContextFlagsValues : uint + { + CONTEXT_AMD = 0x00100000, + CONTEXT_CONTROL = CONTEXT_AMD | 0x00000001, + CONTEXT_INTEGER = CONTEXT_AMD | 0x00000002, + CONTEXT_SEGMENTS = CONTEXT_AMD | 0x00000004, + CONTEXT_FLOATING_POINT = CONTEXT_AMD | 0x00000008, + CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD | 0x00000010, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, + CONTEXT_XSTATE = CONTEXT_AMD | 0x00000040, + CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x00000080, + } + + public static uint Size => 0x4d0; + public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public TargetNUInt StackPointer => new TargetNUInt(Rsp); + public TargetNUInt InstructionPointer => new TargetNUInt(Rip); + + public override string ToString() + { + StringBuilder sb = new(); + foreach (FieldInfo fieldInfo in typeof(AMD64Context).GetFields()) + { + switch (fieldInfo.GetValue(this)) + { + case ulong v: + sb.AppendLine($"{fieldInfo.Name} = {v:x16}"); + break; + case uint v: + sb.AppendLine($"{fieldInfo.Name} = {v:x8}"); + break; + case ushort v: + sb.AppendLine($"{fieldInfo.Name} = {v:x4}"); + break; + default: + sb.AppendLine($"{fieldInfo.Name} = {fieldInfo.GetValue(this)}"); + continue; + } + } + return sb.ToString(); + } + + [FieldOffset(0x0)] + public ulong P1Home; + + [FieldOffset(0x8)] + public ulong P2Home; + + [FieldOffset(0x10)] + public ulong P3Home; + + [FieldOffset(0x18)] + public ulong P4Home; + + [FieldOffset(0x20)] + public ulong P5Home; + + [FieldOffset(0x28)] + public ulong P6Home; + + [FieldOffset(0x30)] + public uint ContextFlags; + + [FieldOffset(0x34)] + public uint MxCsr; + + #region Segment registers + + [Register(RegisterType.Segments)] + [FieldOffset(0x38)] + public ushort Cs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x3a)] + public ushort Ds; + + [Register(RegisterType.Segments)] + [FieldOffset(0x3c)] + public ushort Es; + + [Register(RegisterType.Segments)] + [FieldOffset(0x3e)] + public ushort Fs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x40)] + public ushort Gs; + + [Register(RegisterType.Segments)] + [FieldOffset(0x42)] + public ushort Ss; + + #endregion + + [Register(RegisterType.General)] + [FieldOffset(0x44)] + public int EFlags; + + #region Debug registers + + [Register(RegisterType.Debug)] + [FieldOffset(0x48)] + public ulong Dr0; + + [Register(RegisterType.Debug)] + [FieldOffset(0x50)] + public ulong Dr1; + + [Register(RegisterType.Debug)] + [FieldOffset(0x58)] + public ulong Dr2; + + [Register(RegisterType.Debug)] + [FieldOffset(0x60)] + public ulong Dr3; + + [Register(RegisterType.Debug)] + [FieldOffset(0x68)] + public ulong Dr6; + + [Register(RegisterType.Debug)] + [FieldOffset(0x70)] + public ulong Dr7; + + #endregion + + #region General and control registers + + [Register(RegisterType.General)] + [FieldOffset(0x78)] + public ulong Rax; + + [Register(RegisterType.General)] + [FieldOffset(0x80)] + public ulong Rcx; + + [Register(RegisterType.General)] + [FieldOffset(0x88)] + public ulong Rdx; + + [Register(RegisterType.General)] + [FieldOffset(0x90)] + public ulong Rbx; + + [Register(RegisterType.Control | RegisterType.StackPointer)] + [FieldOffset(0x98)] + public ulong Rsp; + + [Register(RegisterType.Control | RegisterType.FramePointer)] + [FieldOffset(0xa0)] + public ulong Rbp; + + [Register(RegisterType.General)] + [FieldOffset(0xa8)] + public ulong Rsi; + + [Register(RegisterType.General)] + [FieldOffset(0xb0)] + public ulong Rdi; + + [Register(RegisterType.General)] + [FieldOffset(0xb8)] + public ulong R8; + + [Register(RegisterType.General)] + [FieldOffset(0xc0)] + public ulong R9; + + [Register(RegisterType.General)] + [FieldOffset(0xc8)] + public ulong R10; + + [Register(RegisterType.General)] + [FieldOffset(0xd0)] + public ulong R11; + + [Register(RegisterType.General)] + [FieldOffset(0xd8)] + public ulong R12; + + [Register(RegisterType.General)] + [FieldOffset(0xe0)] + public ulong R13; + + [Register(RegisterType.General)] + [FieldOffset(0xe8)] + public ulong R14; + + [Register(RegisterType.General)] + [FieldOffset(0xf0)] + public ulong R15; + + [Register(RegisterType.Control | RegisterType.ProgramCounter)] + [FieldOffset(0xf8)] + public ulong Rip; + + #endregion + + #region Floating point registers + + // [Register(RegisterType.FloatPoint)] + // [FieldOffset(0x100)] + // public XmmSaveArea FltSave; + + // [Register(RegisterType.FloatPoint)] + // [FieldOffset(0x300)] + // public VectorRegisterArea VectorRegisters; + + #endregion + + [Register(RegisterType.Debug)] + [FieldOffset(0x4a8)] + public ulong DebugControl; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4b0)] + public ulong LastBranchToRip; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4b8)] + public ulong LastBranchFromRip; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4c0)] + public ulong LastExceptionToRip; + + [Register(RegisterType.Debug)] + [FieldOffset(0x4c8)] + public ulong LastExceptionFromRip; +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs similarity index 54% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs index 1fadd69ab979fe..f1e0f32e6cf2b1 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalking/FrameIterator.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Diagnostics.DataContractReader.Data; @@ -9,31 +12,51 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal sealed class FrameIterator { - public static void EnumerateFrames(Target target, TargetPointer framePointer) + public static IEnumerable EnumerateFrames(Target target, TargetPointer framePointer) { - string outputhPath = "C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt"; - using StreamWriter writer = new StreamWriter(outputhPath); - Console.SetOut(writer); - TargetPointer terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); while (framePointer != terminator) { Data.Frame frame = target.ProcessedData.GetOrAdd(framePointer); - HandleFrame(target, framePointer); + yield return frame; framePointer = frame.Next; } + } - writer.Flush(); + public static bool TryGetContext(Target target, Data.Frame frame, [NotNullWhen(true)] out TargetPointer? IP, [NotNullWhen(true)] out TargetPointer? SP) + { + switch (frame.Type) + { + case DataType.InlinedCallFrame: + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); + IP = inlinedCallFrame.CallerReturnAddress; + SP = inlinedCallFrame.CallSiteSP; + return true; + case DataType.HelperMethodFrame: + case DataType.HelperMethodFrame_1OBJ: + case DataType.HelperMethodFrame_2OBJ: + case DataType.HelperMethodFrame_3OBJ: + case DataType.HelperMethodFrame_PROTECTOBJ: + Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(frame.Address); + IP = helperMethodFrame.LazyMachState.InstructionPointer; + SP = helperMethodFrame.LazyMachState.StackPointer; + return true; + default: + IP = null; + SP = null; + Console.WriteLine($"Unable to parse frame further: {frame.Type}"); + break; + } + return false; } - public static void HandleFrame(Target target, TargetPointer framePointer) + public static void PrintFrame(Target target, Data.Frame frame) { - Data.Frame frame = target.ProcessedData.GetOrAdd(framePointer); switch (frame.Type) { case DataType.InlinedCallFrame: - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(framePointer); + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); Print(inlinedCallFrame); break; case DataType.HelperMethodFrame: @@ -41,11 +64,11 @@ public static void HandleFrame(Target target, TargetPointer framePointer) case DataType.HelperMethodFrame_2OBJ: case DataType.HelperMethodFrame_3OBJ: case DataType.HelperMethodFrame_PROTECTOBJ: - Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(framePointer); + Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(frame.Address); Print(helperMethodFrame); break; default: - Console.WriteLine($"Unknown frame type: {frame.Type}"); + Console.WriteLine($"Unable to parse frame further: {frame.Type}"); break; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs new file mode 100644 index 00000000000000..57385e4ccab375 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs @@ -0,0 +1,17 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +public interface IContext +{ + + public static uint Size { get; } + public static uint DefaultContextFlags { get; } + + public TargetNUInt StackPointer { get; } + public TargetNUInt InstructionPointer { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/RegisterAttribute.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/RegisterAttribute.cs new file mode 100644 index 00000000000000..1ae0c32bf7ffa4 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/RegisterAttribute.cs @@ -0,0 +1,41 @@ +// 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; + +[Flags] +public enum RegisterType : byte +{ + General = 0x01, + Control = 0x02, + Segments = 0x03, + FloatingPoint = 0x04, + Debug = 0x05, + TypeMask = 0x0f, + + ProgramCounter = 0x10, + StackPointer = 0x20, + FramePointer = 0x40, +} + + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public sealed class RegisterAttribute : Attribute +{ + /// + /// Gets or sets optional name override + /// + public string? Name { get; set; } + + /// + /// Gets register type and flags + /// + public RegisterType RegisterType { get; } + + public RegisterAttribute(RegisterType registerType) + { + RegisterType = registerType; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs new file mode 100644 index 00000000000000..813372fef0ea7d --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs @@ -0,0 +1,18 @@ +// 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; + +internal sealed class StackWalkFactory : IContractFactory +{ + IStackWalk IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new StackWalk_1(target), + _ => default(StackWalk), + }; + } +} 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 new file mode 100644 index 00000000000000..47a53c4b5f462a --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -0,0 +1,91 @@ +// 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.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct StackWalk_1 : IStackWalk +{ + private readonly Target _target; + + internal StackWalk_1(Target target) + { + _target = target; + } + + private AMD64Context GetThreadContext(ThreadData threadData) + { + int hr; + + unsafe + { + byte[] bytes = new byte[AMD64Context.Size]; + + fixed (byte* ptr = bytes) + { + Span buffer = bytes; + hr = _target.GetThreadContext((uint)threadData.OSId.Value, AMD64Context.DefaultContextFlags, AMD64Context.Size, buffer); + } + + if (hr != 0) + { + throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); + } + + AMD64Context context = Marshal.PtrToStructure((IntPtr)Unsafe.AsPointer(ref bytes[0])); + return context; + } + } + + void IStackWalk.TestEntry() + { + string outputhPath = "C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt"; + using StreamWriter writer = new StreamWriter(outputhPath); + Console.SetOut(writer); + + ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); + ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); + + IExecutionManager eman = _target.Contracts.ExecutionManager; + + AMD64Context context = GetThreadContext(threadData); + Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + CheckIP(new(context.InstructionPointer.Value)); + + foreach (Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) + { + FrameIterator.PrintFrame(_target, frame); + if (FrameIterator.TryGetContext(_target, frame, out TargetPointer? IP, out TargetPointer? SP)) + { + CheckIP(new(IP.Value)); + } + } + + + Console.WriteLine(context.ToString()); + + writer.Flush(); + } + + private void CheckIP(TargetCodePointer ip) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + if (eman.GetCodeBlockHandle(ip) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, ip); + Console.WriteLine($"MethodDesc: {methodDesc.Value:x16} BaseAddress: {moduleBase.Value:x16} UnwindInfo: {unwindInfo.Value:x16}"); + } + else + { + Console.WriteLine("IP is unmanaged"); + } + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 29779a7216c205..5fe086a16400bd 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -80,51 +80,4 @@ private TargetPointer GetThreadFromLink(TargetPointer threadLink) // Get the address of the thread containing the link return new TargetPointer(threadLink - _threadLinkOffset); } - - [Flags] - public enum AMD64ContextFlags : uint - { - CONTEXT_AMD = 0x00100000, - CONTEXT_CONTROL = CONTEXT_AMD | 0x00000001, - CONTEXT_INTEGER = CONTEXT_AMD | 0x00000002, - CONTEXT_SEGMENTS = CONTEXT_AMD | 0x00000004, - CONTEXT_FLOATING_POINT = CONTEXT_AMD | 0x00000008, - CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD | 0x00000010, - CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, - CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, - CONTEXT_XSTATE = CONTEXT_AMD | 0x00000040, - CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x00000080, - } - - int IThread.GetThreadContext(TargetPointer threadPointer) - { - ThreadData threadData = ((IThread)this).GetThreadData(threadPointer); - int hr; - - FrameIterator.EnumerateFrames(_target, threadData.Frame); - - TargetPointer framePointer = threadData.Frame; - while (framePointer != new TargetPointer(ulong.MaxValue)) - { - Data.Frame frame = _target.ProcessedData.GetOrAdd(framePointer); - Console.WriteLine(frame.Type); - framePointer = frame.Next; - } - - unsafe - { - byte[] bytes = new byte[0x700]; - - fixed (byte* ptr = bytes) - { - Span buffer = bytes; - hr = _target.GetThreadContext((uint)threadData.OSId.Value, (uint)AMD64ContextFlags.CONTEXT_FULL, 0x700, buffer); - - Console.WriteLine(); - } - } - - - return hr; - } } 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 d137b737112db9..81b5ec7a39fdc1 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 @@ -8,15 +8,40 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class Frame : IData { - private static readonly List SupportedFrameTypes = [ - "InlinedCallFrame", - "HelperMethodFrame", - "HelperMethodFrame_1OBJ", - "HelperMethodFrame_2OBJ", - "HelperMethodFrame_3OBJ", - "HelperMethodFrame_PROTECTOBJ", - "DebuggerU2MCatchHandlerFrame", - "DynamicHelperFrame", + private static readonly List SupportedFrameTypes = [ + DataType.InlinedCallFrame, + DataType.HelperMethodFrame, + DataType.HelperMethodFrame_1OBJ, + DataType.HelperMethodFrame_2OBJ, + DataType.HelperMethodFrame_3OBJ, + DataType.HelperMethodFrame_PROTECTOBJ, + + DataType.ResumableFrame, + DataType.RedirectedTHreadFrame, + DataType.FaultingExceptionFrame, + DataType.SoftwareExceptionFrame, + DataType.FuncEvalFrame, + DataType.UnmanagedToManagedFrame, + DataType.ComMethodFrame, + DataType.CLRToCOMMethodFrame, + DataType.ComPrestubMethodFrame, + DataType.PInvokeCalliFrame, + DataType.HijackFrame, + DataType.PrestubMethodFrame, + DataType.CallCountingHelperFrame, + DataType.StubDispatchFrame, + DataType.ExternalMethodFrame, + DataType.DynamicHelperFrame, + DataType.InterpreterFrame, + DataType.ProtectByRefsFrame, + DataType.ProtectValueClassFrame, + DataType.DebuggerClassInitMarkFrame, + DataType.DebuggerSecurityCodeMarkFrame, + DataType.DebuggerExitFrame, + DataType.DebuggerU2MCatchHandlerFrame, + DataType.TailCallFrame, + DataType.ExceptionFilterFrame, + DataType.AssumeByrefFromJITStack, ]; static Frame IData.Create(Target target, TargetPointer address) @@ -24,6 +49,7 @@ static Frame IData.Create(Target target, TargetPointer address) 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); Type = FindType(target, address); @@ -33,18 +59,28 @@ private static DataType FindType(Target target, TargetPointer address) { TargetPointer instanceVptr = target.ReadPointer(address); - foreach (string frameTypeName in SupportedFrameTypes) + foreach (DataType frameType in SupportedFrameTypes) { - TargetPointer typeVptr = target.ReadGlobalPointer(frameTypeName + "VPtr"); + TargetPointer typeVptr; + try + { + // not all Frames are in all builds, so we need to catch the exception + typeVptr = target.ReadGlobalPointer(frameType.ToString() + "VPtr"); + } + catch (InvalidOperationException) + { + continue; + } if (instanceVptr == typeVptr) { - return Enum.TryParse(frameTypeName, out DataType type) ? type : DataType.Unknown; + return frameType; } } return DataType.Unknown; } + public TargetPointer Address { get; init; } public TargetPointer Next { get; init; } public DataType Type { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index 22a33b4820e326..481b8eccf32151 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -12,7 +12,15 @@ public RealCodeHeader(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + + if (type.Fields.ContainsKey(nameof(NumUnwindInfos))) + NumUnwindInfos = target.Read(address + (ulong)type.Fields[nameof(NumUnwindInfos)].Offset); + + if (type.Fields.ContainsKey(nameof(UnwindInfos))) + UnwindInfos = address + (ulong)type.Fields[nameof(UnwindInfos)].Offset; } public TargetPointer MethodDesc { get; init; } + public uint? NumUnwindInfos { get; init; } + public TargetPointer? UnwindInfos { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs index f87da0de3f07f9..f614803e0efabc 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RuntimeFunction.cs @@ -19,7 +19,7 @@ public RuntimeFunction(Target target, TargetPointer address) EndAddress = target.Read(address + (ulong)type.Fields[nameof(EndAddress)].Offset); UnwindData = target.Read(address + (ulong)type.Fields[nameof(UnwindData)].Offset); - } + } public uint BeginAddress { get; } public uint? EndAddress { get; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index f2602ce5e1e6ca..85360b00b7ec4c 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -37,6 +37,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IPlatformMetadata)] = new PlatformMetadataFactory(), [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), [typeof(IReJIT)] = new ReJITFactory(), + [typeof(IStackWalk)] = new StackWalkFactory(), }; configureFactories?.Invoke(_factories); } @@ -53,6 +54,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IPlatformMetadata PlatformMetadata => GetContract(); public override IPrecodeStubs PrecodeStubs => GetContract(); public override IReJIT ReJIT => GetContract(); + public override IStackWalk StackWalk => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index bda1c27a37fc67..e93650f7f353d1 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -195,16 +195,6 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes return HResults.E_INVALIDARG; } - try - { - ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); - _target.Contracts.Thread.GetThreadContext(tsdata.FirstThread); - } - catch - { - Console.WriteLine("uh oh"); - } - int hr = HResults.S_OK; try { @@ -579,7 +569,18 @@ int ISOSDacInterface.GetMethodDescName(ulong methodDesc, uint count, char* name, int ISOSDacInterface.GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromFrame(frameAddr, ppMD) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) - => _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromIP(ip, ppMD) : HResults.E_NOTIMPL; + { + try + { + _target.Contracts.StackWalk.TestEntry(); + } + catch + { + Console.WriteLine("uh oh"); + } + + return _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromIP(ip, ppMD) : HResults.E_NOTIMPL; + } int ISOSDacInterface.GetMethodDescTransparencyData(ulong methodDesc, void* data) => _legacyImpl is not null ? _legacyImpl.GetMethodDescTransparencyData(methodDesc, data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodTableData(ulong mt, DacpMethodTableData* data) From 0259f83c6f6887617487ae83ac57c4538b7579ac Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Jan 2025 11:39:53 -0500 Subject: [PATCH 04/63] implement R2R unwind info fetching --- .../ExecutionManagerBase.EEJitManager.cs | 3 +- ...ecutionManagerBase.ReadyToRunJitManager.cs | 28 ++++++++++++++++++- .../ExecutionManager/ExecutionManagerBase.cs | 4 +-- .../Helpers/RuntimeFunctionLookup.cs | 7 ++++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs index 7ae720c04e9ace..7246add7e9ad2f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs @@ -42,7 +42,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } - public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPointer imageBase, TargetCodePointer jittedCodeAddress) + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) { // TODO: This only works with funclets enabled. See runtime definition of RealCodeHeader for more info. if (rangeSection.IsRangeList) @@ -71,6 +71,7 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPoi throw new InvalidOperationException("Unable to get NumUnwindInfos"); } + ulong imageBase = rangeSection.Data.RangeBegin; for (ulong i = 0; i < numUnwindInfos; i++) { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs index 3b70339243091f..3f52d84f092759 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.ReadyToRunJitManager.cs @@ -94,7 +94,33 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer return true; } - public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPointer imageBase, TargetCodePointer jittedCodeAddress) => ~0ul; + public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) + { + // ReadyToRunJitManager::JitCodeToMethodInfo + if (rangeSection.Data == null) + throw new ArgumentException(nameof(rangeSection)); + + Debug.Assert(rangeSection.Data.R2RModule != TargetPointer.Null); + + Data.Module r2rModule = Target.ProcessedData.GetOrAdd(rangeSection.Data.R2RModule); + Debug.Assert(r2rModule.ReadyToRunInfo != TargetPointer.Null); + Data.ReadyToRunInfo r2rInfo = Target.ProcessedData.GetOrAdd(r2rModule.ReadyToRunInfo); + + // Check if address is in a thunk + if (IsStubCodeBlockThunk(rangeSection.Data, r2rInfo, jittedCodeAddress)) + return TargetPointer.Null; + + // Find the relative address that we are looking for + TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); + TargetPointer imageBase = rangeSection.Data.RangeBegin; + TargetPointer relativeAddr = addr - imageBase; + + uint index; + if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(r2rInfo.RuntimeFunctions, r2rInfo.NumRuntimeFunctions, relativeAddr, out index)) + return TargetPointer.Null; + + return _runtimeFunctions.GetRuntimeFunctionAddress(r2rInfo.RuntimeFunctions, index); + } private bool IsStubCodeBlockThunk(Data.RangeSection rangeSection, Data.ReadyToRunInfo r2rInfo, TargetCodePointer jittedCodeAddress) { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs index 9ffff1a9b5672d..d9a9a53a93a4d1 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.cs @@ -65,7 +65,7 @@ protected JitManager(Target target) } public abstract bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info); - public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetPointer imageBase, TargetCodePointer jittedCodeAddress); + public abstract TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress); } private sealed class RangeSection @@ -203,6 +203,6 @@ TargetPointer IExecutionManager.GetUnwindInfo(CodeBlockHandle codeInfoHandle, Ta JitManager jitManager = GetJitManager(range.Data); - return jitManager.GetUnwindInfo(range, ((IExecutionManager)this).GetModuleBaseAddress(codeInfoHandle), ip); + return jitManager.GetUnwindInfo(range, ip); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs index e8dbbf6d220909..6db089a81dcb11 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -69,9 +69,14 @@ bool Match(uint index) } } + public TargetPointer GetRuntimeFunctionAddress(TargetPointer runtimeFunctions, uint index) + { + return runtimeFunctions + (index * _runtimeFunctionSize); + } + public Data.RuntimeFunction GetRuntimeFunction(TargetPointer runtimeFunctions, uint index) { - TargetPointer addr = runtimeFunctions + (ulong)(index * _runtimeFunctionSize); + TargetPointer addr = GetRuntimeFunctionAddress(runtimeFunctions, index); return _target.ProcessedData.GetOrAdd(addr); } } From cd8854c8c6da478d7105b4acca0d599b4e593973 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 22 Jan 2025 18:51:28 -0500 Subject: [PATCH 05/63] stackwalking on amd64 --- eng/native/functions.cmake | 14 +- src/coreclr/debug/daccess/cdac.cpp | 2 +- src/coreclr/unwinder/CMakeLists.txt | 11 + src/coreclr/unwinder/amd64/unwinder.cpp | 198 +++++++++++++++++- src/coreclr/unwinder/amd64/unwinder.h | 30 ++- src/coreclr/unwinder/baseunwinder.cpp | 20 ++ src/coreclr/unwinder/baseunwinder.h | 26 ++- src/coreclr/unwinder/stdafx.h | 9 +- .../Contracts/StackWalk/StackWalk_1.cs | 161 +++++++++++++- .../cdacreader/src/Legacy/SOSDacImpl.cs | 5 +- .../managed/cdacreader/src/cdacreader.csproj | 6 + 11 files changed, 452 insertions(+), 30 deletions(-) diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake index 7022a46a7a2778..a1c11ae27430f4 100644 --- a/eng/native/functions.cmake +++ b/eng/native/functions.cmake @@ -515,11 +515,11 @@ function(install_static_library targetName destination component) endif() endfunction() -# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName]) +# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName] [INSTALL_ALL_ARTIFACTS]) function(install_clr) set(multiValueArgs TARGETS DESTINATIONS) set(singleValueArgs COMPONENT) - set(options "") + set(options INSTALL_ALL_ARTIFACTS) cmake_parse_arguments(INSTALL_CLR "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGV}) if ("${INSTALL_CLR_TARGETS}" STREQUAL "") @@ -555,9 +555,13 @@ function(install_clr) endif() foreach(destination ${destinations}) - # We don't need to install the export libraries for our DLLs - # since they won't be directly linked against. - install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + if (${INSTALL_CLR_INSTALL_ALL_ARTIFACTS}) + install(TARGETS ${targetName} DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + else() + # We don't need to install the export libraries for our DLLs + # since they won't be directly linked against. + install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + endif() if (NOT "${symbolFile}" STREQUAL "") install_symbol_file(${symbolFile} ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) endif() diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index 66277f958f66e5..947b8b9d9a26e5 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -25,7 +25,7 @@ namespace iter++; path.Truncate(iter); path.Append(CDAC_LIB_NAME); - *phCDAC = CLRLoadLibrary(path.GetUnicode()); + *phCDAC = CLRLoadLibraryEx(path.GetUnicode(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); if (*phCDAC == NULL) return false; diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index c63712c500e695..6eded4eb7b68ba 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -29,3 +29,14 @@ add_dependencies(unwinder_dac eventing_headers) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) +add_library_clr(unwinder_cdac SHARED ${UNWINDER_SOURCES}) +add_dependencies(unwinder_cdac eventing_headers) +target_link_libraries(unwinder_cdac PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) +target_compile_definitions(unwinder_cdac PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +install_clr(TARGETS unwinder_cdac DESTINATIONS cdaclibs COMPONENT debug INSTALL_ALL_ARTIFACTS) + +add_library_clr(unwinder_cdac_static ${UNWINDER_SOURCES}) +add_dependencies(unwinder_cdac_static eventing_headers) +target_link_libraries(unwinder_cdac_static PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) +target_compile_definitions(unwinder_cdac_static PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +install_clr(TARGETS unwinder_cdac_static DESTINATIONS cdaclibs COMPONENT debug) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index cf4b96ff0b6f8d..85c2e7f30c906e 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -8,6 +8,7 @@ typedef DPTR(M128A) PTR_M128A; +#ifndef FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // // Read 64 bit unsigned value from the specified address. When the unwinder is built @@ -25,7 +26,7 @@ typedef DPTR(M128A) PTR_M128A; // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -static ULONG64 MemoryRead64(PULONG64 addr) +ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) { return *dac_cast((TADDR)addr); } @@ -47,10 +48,11 @@ static ULONG64 MemoryRead64(PULONG64 addr) // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -static M128A MemoryRead128(PM128A addr) +M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } +#endif // !FEATURE_CDAC_UNWINDER #ifdef DACCESS_COMPILE @@ -204,7 +206,8 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) BOOL DacUnwindStackFrame(CONTEXT * pContext, KNONVOLATILE_CONTEXT_POINTERS* pContextPointers) { - BOOL res = OOPStackUnwinderAMD64::Unwind(pContext); + OOPStackUnwinderAMD64 unwinder; + BOOL res = unwinder.Unwind(pContext); if (res && pContextPointers) { @@ -257,6 +260,183 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) return (hr == S_OK); } +#elif defined(FEATURE_CDAC_UNWINDER) + +#define UNWINDER_ASSERT(x) + +//--------------------------------------------------------------------------------------- +// +// The InstructionBuffer class abstracts accessing assembler instructions in the function +// being unwound. It behaves as a memory byte pointer, but it reads the instruction codes +// from the target process being debugged and removes all changes that the debugger +// may have made to the code, e.g. breakpoint instructions. +// +class InstructionBuffer +{ + UINT m_offset; + SIZE_T m_address; + UCHAR m_buffer[32]; + + ReadCallback readCallback; + + // Load the instructions from the target process being debugged + HRESULT Load() + { + HRESULT hr = readCallback(m_address, m_buffer, sizeof(m_buffer)); + if (SUCCEEDED(hr)) + { + // TODO: Implement for cDAC + + // On X64, we need to replace any patches which are within the requested memory range. + // This is because the X64 unwinder needs to disassemble the native instructions in order to determine + // whether the IP is in an epilog. + } + + return hr; + } + +public: + + // Construct the InstructionBuffer for the given address in the target process + InstructionBuffer(SIZE_T address, ReadCallback readCallback) + : m_offset(0), + m_address(address), + readCallback(readCallback) + { + HRESULT hr = Load(); + if (FAILED(hr)) + { + // If we have failed to read from the target process, just pretend + // we've read zeros. + // The InstructionBuffer is used in code driven epilogue unwinding + // when we read processor instructions and simulate them. + // It's very rare to be stopped in an epilogue when + // getting a stack trace, so if we can't read the + // code just assume we aren't in an epilogue instead of failing + // the unwind. + memset(m_buffer, 0, sizeof(m_buffer)); + } + } + + // Move to the next byte in the buffer + InstructionBuffer& operator++() + { + m_offset++; + return *this; + } + + // Skip delta bytes in the buffer + InstructionBuffer& operator+=(INT delta) + { + m_offset += delta; + return *this; + } + + // Return address of the current byte in the buffer + explicit operator ULONG64() + { + return m_address + m_offset; + } + + // Get the byte at the given index from the current position + // Invoke DacError if the index is out of the buffer + UCHAR operator[](int index) + { + int realIndex = m_offset + index; + UNWINDER_ASSERT(realIndex < (int)sizeof(m_buffer)); + return m_buffer[realIndex]; + } +}; + +BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) +{ + HRESULT hr = E_FAIL; + + OOPStackUnwinderAMD64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; + hr = unwinder.Unwind((CONTEXT*) pContext); + + return (hr == S_OK); +} + +BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) +{ + HRESULT hr = E_FAIL; + + ULONG64 uControlPC = pContext->Rip; + + // get the module base + ULONG64 uImageBase; + hr = GetModuleBase(uControlPC, &uImageBase); + if (FAILED(hr)) + { + return FALSE; + } + + // get the function entry + IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; + hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); + if (FAILED(hr)) + { + return FALSE; + } + + // call VirtualUnwind() to do the real work + ULONG64 EstablisherFrame; + hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + + return (hr == S_OK); +} + +UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) +{ + UNWIND_INFO unwindInfo; + if(readCallback((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo)) != S_OK) + { + return NULL; + } + + DWORD cbUnwindInfo = offsetof(UNWIND_INFO, UnwindCode) + + unwindInfo.CountOfUnwindCodes * sizeof(UNWIND_CODE); + + // Check if there is a chained unwind info. If so, it has an extra RUNTIME_FUNCTION tagged to the end. + if ((unwindInfo.Flags & UNW_FLAG_CHAININFO) != 0) + { + // If there is an odd number of UNWIND_CODE, we need to adjust for alignment. + if ((unwindInfo.CountOfUnwindCodes & 1) != 0) + { + cbUnwindInfo += sizeof(UNWIND_CODE); + } + cbUnwindInfo += sizeof(T_RUNTIME_FUNCTION); + } + + UNWIND_INFO* pUnwindInfo; + if(getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo) != S_OK) + { + return NULL; + } + + if(readCallback(taUnwindInfo, pUnwindInfo, cbUnwindInfo) != S_OK) + { + return NULL; + } + + return pUnwindInfo; +} + +ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) +{ + ULONG64 value; + readCallback((uint64_t)addr, &value, sizeof(value)); + return value; +} + +M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) +{ + M128A value; + readCallback((uint64_t)addr, &value, sizeof(value)); + return value; +} + #else // DACCESS_COMPILE // Report failure in the unwinder if the condition is FALSE @@ -275,6 +455,7 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) return (UNWIND_INFO *)taUnwindInfo; } +#ifndef FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // // This function behaves like the RtlVirtualUnwind in Windows. @@ -333,7 +514,8 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( { PEXCEPTION_ROUTINE handlerRoutine; - HRESULT res = OOPStackUnwinderAMD64::VirtualUnwind( + OOPStackUnwinderAMD64 unwinder; + HRESULT res = unwinder.VirtualUnwind( HandlerType, ImageBase, ControlPc, @@ -344,11 +526,11 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( ContextPointers, &handlerRoutine); - _ASSERTE(SUCCEEDED(res)); + UNWINDER_ASSERT(SUCCEEDED(res)); return handlerRoutine; } - +#endif // FEATURE_CDAC_UNWINDER #endif // DACCESS_COMPILE @@ -1206,7 +1388,11 @@ Routine Description: InEpilogue = FALSE; if (UnwindVersion < 2) { +#ifndef FEATURE_CDAC_UNWINDER InstructionBuffer InstrBuffer = (InstructionBuffer)ControlPc; +#else // !FEATURE_CDAC_UNWINDER + InstructionBuffer InstrBuffer(ControlPc, readCallback); +#endif // FEATURE_CDAC_UNWINDER InstructionBuffer NextByte = InstrBuffer; // diff --git a/src/coreclr/unwinder/amd64/unwinder.h b/src/coreclr/unwinder/amd64/unwinder.h index 1c714224f32eb5..cf204abc3e5513 100644 --- a/src/coreclr/unwinder/amd64/unwinder.h +++ b/src/coreclr/unwinder/amd64/unwinder.h @@ -8,6 +8,9 @@ #include "baseunwinder.h" +#ifdef FEATURE_CDAC_UNWINDER +EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -16,15 +19,22 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder { +#ifdef FEATURE_CDAC_UNWINDER +public: + OOPStackUnwinderAMD64(ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) + : OOPStackUnwinder(readCallback, getAllocatedBuffer, getStackWalkInfo) + { } +#endif // FEATURE_CDAC_UNWINDER + public: // Unwind the given CONTEXT to the caller CONTEXT. The CONTEXT will be overwritten. - static BOOL Unwind(CONTEXT * pContext); + BOOL Unwind(CONTEXT * pContext); // // Everything below comes from dbghelp.dll. // - static HRESULT VirtualUnwind(_In_ DWORD HandlerType, + HRESULT VirtualUnwind(_In_ DWORD HandlerType, _In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, @@ -36,16 +46,16 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder protected: - static ULONG UnwindOpSlots(_In_ UNWIND_CODE UnwindCode); + ULONG UnwindOpSlots(_In_ UNWIND_CODE UnwindCode); - static HRESULT UnwindEpilogue(_In_ ULONG64 ImageBase, + HRESULT UnwindEpilogue(_In_ ULONG64 ImageBase, _In_ ULONG64 ControlPc, _In_ ULONG EpilogueOffset, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, __inout PCONTEXT ContextRecord, __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers); - static HRESULT UnwindPrologue(_In_ DWORD64 ImageBase, + HRESULT UnwindPrologue(_In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc, _In_ DWORD64 FrameBase, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, @@ -53,16 +63,20 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, _Outptr_ _PIMAGE_RUNTIME_FUNCTION_ENTRY *FinalFunctionEntry); - static _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry + _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry (_In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, _In_ DWORD64 ImageBase); - static _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction + _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction (_In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, _In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc); - static UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); + UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); + +public: + ULONG64 MemoryRead64(PULONG64 addr); + M128A MemoryRead128(PM128A addr); }; #endif // __unwinder_amd64_h__ diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index b00c2aa114835e..36c4c6cab67389 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -6,9 +6,11 @@ #include "stdafx.h" #include "baseunwinder.h" +#ifndef FEATURE_CDAC_UNWINDER EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, OUT UINT_PTR* pModuleBase, OUT UINT_PTR* pFuncEntry); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -27,7 +29,11 @@ EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, _Out_ PDWORD64 pdwBase) { +#ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); +#else // FEATURE_CDAC_UNWINDER + getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); +#endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } @@ -56,6 +62,7 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres } PVOID pFuncEntry = NULL; +#ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry)); if (pFuncEntry == NULL) { @@ -64,4 +71,17 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres memcpy(pBuffer, pFuncEntry, cbBuffer); return S_OK; +#else // FEATURE_CDAC_UNWINDER + getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry)); + if (pFuncEntry == NULL) + { + return E_FAIL; + } + if (readCallback((DWORD64)pFuncEntry, pBuffer, cbBuffer) != S_OK) + { + return E_FAIL; + } + + return S_OK; +#endif } diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 241dc8a7ddfb30..2411217e3a50a4 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -6,6 +6,11 @@ #ifndef __unwinder_h__ #define __unwinder_h__ +#ifdef FEATURE_CDAC_UNWINDER +using ReadCallback = int (*)(uint64_t addr, void* pBuffer, int bufferSize); +using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer); +using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -28,13 +33,30 @@ class OOPStackUnwinder // Given a control PC, return the base of the module it is in. For jitted managed code, this is the // start of the code heap. - static HRESULT GetModuleBase( DWORD64 address, + HRESULT GetModuleBase(uint64_t address, _Out_ PDWORD64 pdwBase); // Given a control PC, return the function entry of the functoin it is in. - static HRESULT GetFunctionEntry( DWORD64 address, + HRESULT GetFunctionEntry( DWORD64 address, _Out_writes_(cbBuffer) PVOID pBuffer, DWORD cbBuffer); + +#ifdef FEATURE_CDAC_UNWINDER +protected: + + OOPStackUnwinder(ReadCallback readCallback, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo) + : readCallback(readCallback), + getAllocatedBuffer(getAllocatedBuffer), + getStackWalkInfo(getStackWalkInfo) + { } + + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + +#endif // FEATURE_CDAC_UWNINDER }; #endif // __unwinder_h__ diff --git a/src/coreclr/unwinder/stdafx.h b/src/coreclr/unwinder/stdafx.h index 8decdc68562bd4..e0b75a561fb57a 100644 --- a/src/coreclr/unwinder/stdafx.h +++ b/src/coreclr/unwinder/stdafx.h @@ -10,10 +10,17 @@ #define USE_COM_CONTEXT_DEF +#ifndef FEATURE_CDAC_UNWINDER #include - #include #include +#else // !FEATURE_CDAC_UNWINDER +#include +#include +#include +#include +#endif + #ifdef DACCESS_COMPILE #include #include 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 47a53c4b5f462a..3071352e225356 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 @@ -3,10 +3,12 @@ using System; using System.IO; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -using Microsoft.Diagnostics.DataContractReader.Data; +using Microsoft.Diagnostics.DataContractReader; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -58,18 +60,15 @@ void IStackWalk.TestEntry() Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); CheckIP(new(context.InstructionPointer.Value)); - foreach (Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) + foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) { FrameIterator.PrintFrame(_target, frame); if (FrameIterator.TryGetContext(_target, frame, out TargetPointer? IP, out TargetPointer? SP)) { - CheckIP(new(IP.Value)); + UnwindUntilNative(IP.Value, SP.Value); } } - - Console.WriteLine(context.ToString()); - writer.Flush(); } @@ -88,4 +87,154 @@ private void CheckIP(TargetCodePointer ip) Console.WriteLine("IP is unmanaged"); } } + + private void UnwindUntilNative(TargetPointer ip, TargetPointer sp) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + + AMD64Context context = new AMD64Context() + { + Rsp = sp, + Rip = ip, + }; + while (eman.GetCodeBlockHandle(new(context.Rip)) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, new(context.Rip)); + Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16} MethodDesc: {methodDesc.Value:x16}"); + try + { + Unwinder.AMD64Unwind(ref context, moduleBase.Value, new IntPtr((long)unwindInfo.Value), _target); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16}"); + Console.WriteLine($"IP is unmanaged, finishing unwind started at {sp.Value:x16}"); + } + + private void Unwind(TargetPointer ip, TargetPointer sp) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + + if (eman.GetCodeBlockHandle(new(ip.Value)) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, new(ip.Value)); + Console.WriteLine($"MethodDesc: {methodDesc.Value:x16} BaseAddress: {moduleBase.Value:x16} UnwindInfo: {unwindInfo.Value:x16}"); + try + { + AMD64Context context = new AMD64Context() + { + Rsp = sp, + Rip = ip, + }; + Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + Unwinder.AMD64Unwind(ref context, moduleBase.Value, new IntPtr((long)unwindInfo.Value), _target); + Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + } + } + else + { + Console.WriteLine("IP is unmanaged"); + } + } +}; + +internal static unsafe partial class Unwinder +{ + [LibraryImport("unwinder_cdac", EntryPoint = "timesTwo")] + public static partial int TimesTwo(int x); + + // ReadCallback allows the unwinder to read memory from the target process + // into an allocated buffer. This buffer is either allocated by the unwinder + // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. + // In the latter case, the unwinder can only use the buffer for the duration of the + // unwind call. Once the call is over the cDAC will free all allocated buffers. + public delegate int ReadCallback(ulong address, void* buffer, int bufferSize); + public delegate int GetAllocatedBuffer(int bufferSize, void** buffer); + + // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp + // To maintain the same signature as the original function, we return void. + // If the moduleBase or funcEntry can not be found, both will be 0. + public delegate void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry); + + [LibraryImport("unwinder_cdac", EntryPoint = "amd64Unwind")] + private static partial int AMD64Unwind( + ref AMD64Context context, + [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, + [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, + [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + + public static int AMD64Unwind( + ref AMD64Context context, + ulong moduleBase, + IntPtr functionEntry, + Target target) + { + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + + // Move to IDisposable for freeing + List allocatedRegions = []; + + readCallback = (address, pBuffer, bufferSize) => + { + Span span = new Span(pBuffer, bufferSize); + target.ReadBuffer(address, span); + return 0; + }; + getAllocatedBuffer = (bufferSize, ppBuffer) => + { + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + IntPtr pBuffer = new(*ppBuffer); + //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); + allocatedRegions.Add(pBuffer); + return 0; + }; + getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + { + IExecutionManager eman = target.Contracts.ExecutionManager; + + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + } + }; + + int ret = AMD64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); + + foreach (IntPtr ptr in allocatedRegions) + { + //Console.WriteLine($"Freeing buffer at {ptr:x16}"); + NativeMemory.Free(ptr.ToPointer()); + } + + return ret; + } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e93650f7f353d1..3dccfab0865bb2 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -572,7 +572,10 @@ int ISOSDacInterface.GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) { try { - _target.Contracts.StackWalk.TestEntry(); + if (ip == 1) + { + _target.Contracts.StackWalk.TestEntry(); + } } catch { diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 2af9e9281927c3..bd66f349d59e76 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -30,6 +30,12 @@ + + + + + + From 17e34fd57bc4b880de80b442c7c5e6cb65e68f6d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 23 Jan 2025 13:24:02 -0500 Subject: [PATCH 06/63] AMD64 stack unwinding impl --- eng/native/functions.cmake | 14 +- src/coreclr/debug/daccess/cdac.cpp | 2 +- src/coreclr/unwinder/CMakeLists.txt | 11 + src/coreclr/unwinder/amd64/unwinder.cpp | 198 +++++++++++++++- src/coreclr/unwinder/amd64/unwinder.h | 30 ++- src/coreclr/unwinder/baseunwinder.cpp | 20 ++ src/coreclr/unwinder/baseunwinder.h | 26 +- src/coreclr/unwinder/stdafx.h | 9 +- .../Contracts/StackWalk/AMD64Context.cs | 7 +- .../Contracts/StackWalk/ARM64Context.cs | 224 ++++++++++++++++++ .../Contracts/StackWalk/IContext.cs | 5 +- .../Contracts/StackWalk/StackWalk_1.cs | 161 ++++++++++++- .../cdacreader/src/Legacy/SOSDacImpl.cs | 5 +- .../managed/cdacreader/src/cdacreader.csproj | 6 + 14 files changed, 683 insertions(+), 35 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake index 7022a46a7a2778..a1c11ae27430f4 100644 --- a/eng/native/functions.cmake +++ b/eng/native/functions.cmake @@ -515,11 +515,11 @@ function(install_static_library targetName destination component) endif() endfunction() -# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName]) +# install_clr(TARGETS targetName [targetName2 ...] [DESTINATIONS destination [destination2 ...]] [COMPONENT componentName] [INSTALL_ALL_ARTIFACTS]) function(install_clr) set(multiValueArgs TARGETS DESTINATIONS) set(singleValueArgs COMPONENT) - set(options "") + set(options INSTALL_ALL_ARTIFACTS) cmake_parse_arguments(INSTALL_CLR "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGV}) if ("${INSTALL_CLR_TARGETS}" STREQUAL "") @@ -555,9 +555,13 @@ function(install_clr) endif() foreach(destination ${destinations}) - # We don't need to install the export libraries for our DLLs - # since they won't be directly linked against. - install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + if (${INSTALL_CLR_INSTALL_ALL_ARTIFACTS}) + install(TARGETS ${targetName} DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + else() + # We don't need to install the export libraries for our DLLs + # since they won't be directly linked against. + install(PROGRAMS $ DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) + endif() if (NOT "${symbolFile}" STREQUAL "") install_symbol_file(${symbolFile} ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) endif() diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index 66277f958f66e5..947b8b9d9a26e5 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -25,7 +25,7 @@ namespace iter++; path.Truncate(iter); path.Append(CDAC_LIB_NAME); - *phCDAC = CLRLoadLibrary(path.GetUnicode()); + *phCDAC = CLRLoadLibraryEx(path.GetUnicode(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); if (*phCDAC == NULL) return false; diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index c63712c500e695..6eded4eb7b68ba 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -29,3 +29,14 @@ add_dependencies(unwinder_dac eventing_headers) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) +add_library_clr(unwinder_cdac SHARED ${UNWINDER_SOURCES}) +add_dependencies(unwinder_cdac eventing_headers) +target_link_libraries(unwinder_cdac PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) +target_compile_definitions(unwinder_cdac PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +install_clr(TARGETS unwinder_cdac DESTINATIONS cdaclibs COMPONENT debug INSTALL_ALL_ARTIFACTS) + +add_library_clr(unwinder_cdac_static ${UNWINDER_SOURCES}) +add_dependencies(unwinder_cdac_static eventing_headers) +target_link_libraries(unwinder_cdac_static PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) +target_compile_definitions(unwinder_cdac_static PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +install_clr(TARGETS unwinder_cdac_static DESTINATIONS cdaclibs COMPONENT debug) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index cf4b96ff0b6f8d..85c2e7f30c906e 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -8,6 +8,7 @@ typedef DPTR(M128A) PTR_M128A; +#ifndef FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // // Read 64 bit unsigned value from the specified address. When the unwinder is built @@ -25,7 +26,7 @@ typedef DPTR(M128A) PTR_M128A; // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -static ULONG64 MemoryRead64(PULONG64 addr) +ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) { return *dac_cast((TADDR)addr); } @@ -47,10 +48,11 @@ static ULONG64 MemoryRead64(PULONG64 addr) // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -static M128A MemoryRead128(PM128A addr) +M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } +#endif // !FEATURE_CDAC_UNWINDER #ifdef DACCESS_COMPILE @@ -204,7 +206,8 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) BOOL DacUnwindStackFrame(CONTEXT * pContext, KNONVOLATILE_CONTEXT_POINTERS* pContextPointers) { - BOOL res = OOPStackUnwinderAMD64::Unwind(pContext); + OOPStackUnwinderAMD64 unwinder; + BOOL res = unwinder.Unwind(pContext); if (res && pContextPointers) { @@ -257,6 +260,183 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) return (hr == S_OK); } +#elif defined(FEATURE_CDAC_UNWINDER) + +#define UNWINDER_ASSERT(x) + +//--------------------------------------------------------------------------------------- +// +// The InstructionBuffer class abstracts accessing assembler instructions in the function +// being unwound. It behaves as a memory byte pointer, but it reads the instruction codes +// from the target process being debugged and removes all changes that the debugger +// may have made to the code, e.g. breakpoint instructions. +// +class InstructionBuffer +{ + UINT m_offset; + SIZE_T m_address; + UCHAR m_buffer[32]; + + ReadCallback readCallback; + + // Load the instructions from the target process being debugged + HRESULT Load() + { + HRESULT hr = readCallback(m_address, m_buffer, sizeof(m_buffer)); + if (SUCCEEDED(hr)) + { + // TODO: Implement for cDAC + + // On X64, we need to replace any patches which are within the requested memory range. + // This is because the X64 unwinder needs to disassemble the native instructions in order to determine + // whether the IP is in an epilog. + } + + return hr; + } + +public: + + // Construct the InstructionBuffer for the given address in the target process + InstructionBuffer(SIZE_T address, ReadCallback readCallback) + : m_offset(0), + m_address(address), + readCallback(readCallback) + { + HRESULT hr = Load(); + if (FAILED(hr)) + { + // If we have failed to read from the target process, just pretend + // we've read zeros. + // The InstructionBuffer is used in code driven epilogue unwinding + // when we read processor instructions and simulate them. + // It's very rare to be stopped in an epilogue when + // getting a stack trace, so if we can't read the + // code just assume we aren't in an epilogue instead of failing + // the unwind. + memset(m_buffer, 0, sizeof(m_buffer)); + } + } + + // Move to the next byte in the buffer + InstructionBuffer& operator++() + { + m_offset++; + return *this; + } + + // Skip delta bytes in the buffer + InstructionBuffer& operator+=(INT delta) + { + m_offset += delta; + return *this; + } + + // Return address of the current byte in the buffer + explicit operator ULONG64() + { + return m_address + m_offset; + } + + // Get the byte at the given index from the current position + // Invoke DacError if the index is out of the buffer + UCHAR operator[](int index) + { + int realIndex = m_offset + index; + UNWINDER_ASSERT(realIndex < (int)sizeof(m_buffer)); + return m_buffer[realIndex]; + } +}; + +BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) +{ + HRESULT hr = E_FAIL; + + OOPStackUnwinderAMD64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; + hr = unwinder.Unwind((CONTEXT*) pContext); + + return (hr == S_OK); +} + +BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) +{ + HRESULT hr = E_FAIL; + + ULONG64 uControlPC = pContext->Rip; + + // get the module base + ULONG64 uImageBase; + hr = GetModuleBase(uControlPC, &uImageBase); + if (FAILED(hr)) + { + return FALSE; + } + + // get the function entry + IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; + hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); + if (FAILED(hr)) + { + return FALSE; + } + + // call VirtualUnwind() to do the real work + ULONG64 EstablisherFrame; + hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + + return (hr == S_OK); +} + +UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) +{ + UNWIND_INFO unwindInfo; + if(readCallback((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo)) != S_OK) + { + return NULL; + } + + DWORD cbUnwindInfo = offsetof(UNWIND_INFO, UnwindCode) + + unwindInfo.CountOfUnwindCodes * sizeof(UNWIND_CODE); + + // Check if there is a chained unwind info. If so, it has an extra RUNTIME_FUNCTION tagged to the end. + if ((unwindInfo.Flags & UNW_FLAG_CHAININFO) != 0) + { + // If there is an odd number of UNWIND_CODE, we need to adjust for alignment. + if ((unwindInfo.CountOfUnwindCodes & 1) != 0) + { + cbUnwindInfo += sizeof(UNWIND_CODE); + } + cbUnwindInfo += sizeof(T_RUNTIME_FUNCTION); + } + + UNWIND_INFO* pUnwindInfo; + if(getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo) != S_OK) + { + return NULL; + } + + if(readCallback(taUnwindInfo, pUnwindInfo, cbUnwindInfo) != S_OK) + { + return NULL; + } + + return pUnwindInfo; +} + +ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) +{ + ULONG64 value; + readCallback((uint64_t)addr, &value, sizeof(value)); + return value; +} + +M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) +{ + M128A value; + readCallback((uint64_t)addr, &value, sizeof(value)); + return value; +} + #else // DACCESS_COMPILE // Report failure in the unwinder if the condition is FALSE @@ -275,6 +455,7 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) return (UNWIND_INFO *)taUnwindInfo; } +#ifndef FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // // This function behaves like the RtlVirtualUnwind in Windows. @@ -333,7 +514,8 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( { PEXCEPTION_ROUTINE handlerRoutine; - HRESULT res = OOPStackUnwinderAMD64::VirtualUnwind( + OOPStackUnwinderAMD64 unwinder; + HRESULT res = unwinder.VirtualUnwind( HandlerType, ImageBase, ControlPc, @@ -344,11 +526,11 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( ContextPointers, &handlerRoutine); - _ASSERTE(SUCCEEDED(res)); + UNWINDER_ASSERT(SUCCEEDED(res)); return handlerRoutine; } - +#endif // FEATURE_CDAC_UNWINDER #endif // DACCESS_COMPILE @@ -1206,7 +1388,11 @@ Routine Description: InEpilogue = FALSE; if (UnwindVersion < 2) { +#ifndef FEATURE_CDAC_UNWINDER InstructionBuffer InstrBuffer = (InstructionBuffer)ControlPc; +#else // !FEATURE_CDAC_UNWINDER + InstructionBuffer InstrBuffer(ControlPc, readCallback); +#endif // FEATURE_CDAC_UNWINDER InstructionBuffer NextByte = InstrBuffer; // diff --git a/src/coreclr/unwinder/amd64/unwinder.h b/src/coreclr/unwinder/amd64/unwinder.h index 1c714224f32eb5..cf204abc3e5513 100644 --- a/src/coreclr/unwinder/amd64/unwinder.h +++ b/src/coreclr/unwinder/amd64/unwinder.h @@ -8,6 +8,9 @@ #include "baseunwinder.h" +#ifdef FEATURE_CDAC_UNWINDER +EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -16,15 +19,22 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder { +#ifdef FEATURE_CDAC_UNWINDER +public: + OOPStackUnwinderAMD64(ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) + : OOPStackUnwinder(readCallback, getAllocatedBuffer, getStackWalkInfo) + { } +#endif // FEATURE_CDAC_UNWINDER + public: // Unwind the given CONTEXT to the caller CONTEXT. The CONTEXT will be overwritten. - static BOOL Unwind(CONTEXT * pContext); + BOOL Unwind(CONTEXT * pContext); // // Everything below comes from dbghelp.dll. // - static HRESULT VirtualUnwind(_In_ DWORD HandlerType, + HRESULT VirtualUnwind(_In_ DWORD HandlerType, _In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, @@ -36,16 +46,16 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder protected: - static ULONG UnwindOpSlots(_In_ UNWIND_CODE UnwindCode); + ULONG UnwindOpSlots(_In_ UNWIND_CODE UnwindCode); - static HRESULT UnwindEpilogue(_In_ ULONG64 ImageBase, + HRESULT UnwindEpilogue(_In_ ULONG64 ImageBase, _In_ ULONG64 ControlPc, _In_ ULONG EpilogueOffset, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, __inout PCONTEXT ContextRecord, __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers); - static HRESULT UnwindPrologue(_In_ DWORD64 ImageBase, + HRESULT UnwindPrologue(_In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc, _In_ DWORD64 FrameBase, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, @@ -53,16 +63,20 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, _Outptr_ _PIMAGE_RUNTIME_FUNCTION_ENTRY *FinalFunctionEntry); - static _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry + _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry (_In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, _In_ DWORD64 ImageBase); - static _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction + _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction (_In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, _In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc); - static UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); + UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); + +public: + ULONG64 MemoryRead64(PULONG64 addr); + M128A MemoryRead128(PM128A addr); }; #endif // __unwinder_amd64_h__ diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index b00c2aa114835e..36c4c6cab67389 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -6,9 +6,11 @@ #include "stdafx.h" #include "baseunwinder.h" +#ifndef FEATURE_CDAC_UNWINDER EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, OUT UINT_PTR* pModuleBase, OUT UINT_PTR* pFuncEntry); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -27,7 +29,11 @@ EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, _Out_ PDWORD64 pdwBase) { +#ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); +#else // FEATURE_CDAC_UNWINDER + getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); +#endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } @@ -56,6 +62,7 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres } PVOID pFuncEntry = NULL; +#ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry)); if (pFuncEntry == NULL) { @@ -64,4 +71,17 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres memcpy(pBuffer, pFuncEntry, cbBuffer); return S_OK; +#else // FEATURE_CDAC_UNWINDER + getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry)); + if (pFuncEntry == NULL) + { + return E_FAIL; + } + if (readCallback((DWORD64)pFuncEntry, pBuffer, cbBuffer) != S_OK) + { + return E_FAIL; + } + + return S_OK; +#endif } diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 241dc8a7ddfb30..2411217e3a50a4 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -6,6 +6,11 @@ #ifndef __unwinder_h__ #define __unwinder_h__ +#ifdef FEATURE_CDAC_UNWINDER +using ReadCallback = int (*)(uint64_t addr, void* pBuffer, int bufferSize); +using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer); +using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -28,13 +33,30 @@ class OOPStackUnwinder // Given a control PC, return the base of the module it is in. For jitted managed code, this is the // start of the code heap. - static HRESULT GetModuleBase( DWORD64 address, + HRESULT GetModuleBase(uint64_t address, _Out_ PDWORD64 pdwBase); // Given a control PC, return the function entry of the functoin it is in. - static HRESULT GetFunctionEntry( DWORD64 address, + HRESULT GetFunctionEntry( DWORD64 address, _Out_writes_(cbBuffer) PVOID pBuffer, DWORD cbBuffer); + +#ifdef FEATURE_CDAC_UNWINDER +protected: + + OOPStackUnwinder(ReadCallback readCallback, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo) + : readCallback(readCallback), + getAllocatedBuffer(getAllocatedBuffer), + getStackWalkInfo(getStackWalkInfo) + { } + + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + +#endif // FEATURE_CDAC_UWNINDER }; #endif // __unwinder_h__ diff --git a/src/coreclr/unwinder/stdafx.h b/src/coreclr/unwinder/stdafx.h index 8decdc68562bd4..e0b75a561fb57a 100644 --- a/src/coreclr/unwinder/stdafx.h +++ b/src/coreclr/unwinder/stdafx.h @@ -10,10 +10,17 @@ #define USE_COM_CONTEXT_DEF +#ifndef FEATURE_CDAC_UNWINDER #include - #include #include +#else // !FEATURE_CDAC_UNWINDER +#include +#include +#include +#include +#endif + #ifdef DACCESS_COMPILE #include #include diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs index 7e32e936d973c7..91d741d599a4ec 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs @@ -8,6 +8,9 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +/// +/// AMD64-specific thread context. +/// [StructLayout(LayoutKind.Explicit, Pack = 1)] public struct AMD64Context : IContext { @@ -29,8 +32,8 @@ public enum ContextFlagsValues : uint public static uint Size => 0x4d0; public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; - public TargetNUInt StackPointer => new TargetNUInt(Rsp); - public TargetNUInt InstructionPointer => new TargetNUInt(Rip); + public readonly TargetPointer StackPointer => new(Rsp); + public readonly TargetPointer InstructionPointer => new(Rip); public override string ToString() { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs new file mode 100644 index 00000000000000..bd9f20ddc81bfa --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs @@ -0,0 +1,224 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// ARM64-specific thread context. +/// +[StructLayout(LayoutKind.Explicit, Pack = 1)] +public struct ARM64Context : IContext +{ + [Flags] + public enum ContextFlagsValues : uint + { + CONTEXT_ARM64 = 0x00400000, + CONTEXT_CONTROL = CONTEXT_ARM64 | 0x1, + CONTEXT_INTEGER = CONTEXT_ARM64 | 0x2, + CONTEXT_FLOATING_POINT = CONTEXT_ARM64 | 0x4, + CONTEXT_DEBUG_REGISTERS = CONTEXT_ARM64 | 0x8, + CONTEXT_X18 = CONTEXT_ARM64 | 0x10, + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, + } + + public static uint Size => 0x390; + + public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly TargetPointer StackPointer => new(Sp); + public readonly TargetPointer InstructionPointer => new(Pc); + + // Control flags + + [FieldOffset(0x0)] + public uint ContextFlags; + + #region General registers + + [Register(RegisterType.General)] + [FieldOffset(0x4)] + public uint Cpsr; + + [Register(RegisterType.General)] + [FieldOffset(0x8)] + public ulong X0; + + [Register(RegisterType.General)] + [FieldOffset(0x10)] + public ulong X1; + + [Register(RegisterType.General)] + [FieldOffset(0x18)] + public ulong X2; + + [Register(RegisterType.General)] + [FieldOffset(0x20)] + public ulong X3; + + [Register(RegisterType.General)] + [FieldOffset(0x28)] + public ulong X4; + + [Register(RegisterType.General)] + [FieldOffset(0x30)] + public ulong X5; + + [Register(RegisterType.General)] + [FieldOffset(0x38)] + public ulong X6; + + [Register(RegisterType.General)] + [FieldOffset(0x40)] + public ulong X7; + + [Register(RegisterType.General)] + [FieldOffset(0x48)] + public ulong X8; + + [Register(RegisterType.General)] + [FieldOffset(0x50)] + public ulong X9; + + [Register(RegisterType.General)] + [FieldOffset(0x58)] + public ulong X10; + + [Register(RegisterType.General)] + [FieldOffset(0x60)] + public ulong X11; + + [Register(RegisterType.General)] + [FieldOffset(0x68)] + public ulong X12; + + [Register(RegisterType.General)] + [FieldOffset(0x70)] + public ulong X13; + + [Register(RegisterType.General)] + [FieldOffset(0x78)] + public ulong X14; + + [Register(RegisterType.General)] + [FieldOffset(0x80)] + public ulong X15; + + [Register(RegisterType.General)] + [FieldOffset(0x88)] + public ulong X16; + + [Register(RegisterType.General)] + [FieldOffset(0x90)] + public ulong X17; + + [Register(RegisterType.General)] + [FieldOffset(0x98)] + public ulong X18; + + [Register(RegisterType.General)] + [FieldOffset(0xa0)] + public ulong X19; + + [Register(RegisterType.General)] + [FieldOffset(0xa8)] + public ulong X20; + + [Register(RegisterType.General)] + [FieldOffset(0xb0)] + public ulong X21; + + [Register(RegisterType.General)] + [FieldOffset(0xb8)] + public ulong X22; + + [Register(RegisterType.General)] + [FieldOffset(0xc0)] + public ulong X23; + + [Register(RegisterType.General)] + [FieldOffset(0xc8)] + public ulong X24; + + [Register(RegisterType.General)] + [FieldOffset(0xd0)] + public ulong X25; + + [Register(RegisterType.General)] + [FieldOffset(0xd8)] + public ulong X26; + + [Register(RegisterType.General)] + [FieldOffset(0xe0)] + public ulong X27; + + [Register(RegisterType.General)] + [FieldOffset(0xe8)] + public ulong X28; + + #endregion + + #region Control Registers + + [Register(RegisterType.Control | RegisterType.FramePointer)] + [FieldOffset(0xf0)] + public ulong Fp; + + [Register(RegisterType.Control)] + [FieldOffset(0xf8)] + public ulong Lr; + + [Register(RegisterType.Control | RegisterType.StackPointer)] + [FieldOffset(0x100)] + public ulong Sp; + + [Register(RegisterType.Control | RegisterType.ProgramCounter)] + [FieldOffset(0x108)] + public ulong Pc; + + #endregion + + #region Floating Point/NEON Registers + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x110)] + public unsafe fixed ulong V[32 * 2]; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x310)] + public uint Fpcr; + + [Register(RegisterType.FloatingPoint)] + [FieldOffset(0x314)] + public uint Fpsr; + + #endregion + + #region Debug Registers + +#pragma warning disable CA1823 // Avoid unused private fields. See https://github.com/dotnet/roslyn/issues/29224 + private const int ARM64_MAX_BREAKPOINTS = 8; + private const int ARM64_MAX_WATCHPOINTS = 2; +#pragma warning restore CA1823 // Avoid unused private fields + + [Register(RegisterType.Debug)] + [FieldOffset(0x318)] + public unsafe fixed uint Bcr[ARM64_MAX_BREAKPOINTS]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x338)] + public unsafe fixed ulong Bvr[ARM64_MAX_BREAKPOINTS]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x378)] + public unsafe fixed uint Wcr[ARM64_MAX_WATCHPOINTS]; + + [Register(RegisterType.Debug)] + [FieldOffset(0x380)] + public unsafe fixed ulong Wvr[ARM64_MAX_WATCHPOINTS]; + + #endregion +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs index 57385e4ccab375..045d8fc48fcfa4 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs @@ -8,10 +8,9 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; public interface IContext { - public static uint Size { get; } public static uint DefaultContextFlags { get; } - public TargetNUInt StackPointer { get; } - public TargetNUInt InstructionPointer { get; } + public TargetPointer StackPointer { get; } + public TargetPointer InstructionPointer { get; } } 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 47a53c4b5f462a..3071352e225356 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 @@ -3,10 +3,12 @@ using System; using System.IO; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -using Microsoft.Diagnostics.DataContractReader.Data; +using Microsoft.Diagnostics.DataContractReader; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -58,18 +60,15 @@ void IStackWalk.TestEntry() Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); CheckIP(new(context.InstructionPointer.Value)); - foreach (Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) + foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) { FrameIterator.PrintFrame(_target, frame); if (FrameIterator.TryGetContext(_target, frame, out TargetPointer? IP, out TargetPointer? SP)) { - CheckIP(new(IP.Value)); + UnwindUntilNative(IP.Value, SP.Value); } } - - Console.WriteLine(context.ToString()); - writer.Flush(); } @@ -88,4 +87,154 @@ private void CheckIP(TargetCodePointer ip) Console.WriteLine("IP is unmanaged"); } } + + private void UnwindUntilNative(TargetPointer ip, TargetPointer sp) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + + AMD64Context context = new AMD64Context() + { + Rsp = sp, + Rip = ip, + }; + while (eman.GetCodeBlockHandle(new(context.Rip)) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, new(context.Rip)); + Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16} MethodDesc: {methodDesc.Value:x16}"); + try + { + Unwinder.AMD64Unwind(ref context, moduleBase.Value, new IntPtr((long)unwindInfo.Value), _target); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16}"); + Console.WriteLine($"IP is unmanaged, finishing unwind started at {sp.Value:x16}"); + } + + private void Unwind(TargetPointer ip, TargetPointer sp) + { + IExecutionManager eman = _target.Contracts.ExecutionManager; + + if (eman.GetCodeBlockHandle(new(ip.Value)) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, new(ip.Value)); + Console.WriteLine($"MethodDesc: {methodDesc.Value:x16} BaseAddress: {moduleBase.Value:x16} UnwindInfo: {unwindInfo.Value:x16}"); + try + { + AMD64Context context = new AMD64Context() + { + Rsp = sp, + Rip = ip, + }; + Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + Unwinder.AMD64Unwind(ref context, moduleBase.Value, new IntPtr((long)unwindInfo.Value), _target); + Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + } + } + else + { + Console.WriteLine("IP is unmanaged"); + } + } +}; + +internal static unsafe partial class Unwinder +{ + [LibraryImport("unwinder_cdac", EntryPoint = "timesTwo")] + public static partial int TimesTwo(int x); + + // ReadCallback allows the unwinder to read memory from the target process + // into an allocated buffer. This buffer is either allocated by the unwinder + // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. + // In the latter case, the unwinder can only use the buffer for the duration of the + // unwind call. Once the call is over the cDAC will free all allocated buffers. + public delegate int ReadCallback(ulong address, void* buffer, int bufferSize); + public delegate int GetAllocatedBuffer(int bufferSize, void** buffer); + + // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp + // To maintain the same signature as the original function, we return void. + // If the moduleBase or funcEntry can not be found, both will be 0. + public delegate void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry); + + [LibraryImport("unwinder_cdac", EntryPoint = "amd64Unwind")] + private static partial int AMD64Unwind( + ref AMD64Context context, + [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, + [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, + [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + + public static int AMD64Unwind( + ref AMD64Context context, + ulong moduleBase, + IntPtr functionEntry, + Target target) + { + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + + // Move to IDisposable for freeing + List allocatedRegions = []; + + readCallback = (address, pBuffer, bufferSize) => + { + Span span = new Span(pBuffer, bufferSize); + target.ReadBuffer(address, span); + return 0; + }; + getAllocatedBuffer = (bufferSize, ppBuffer) => + { + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + IntPtr pBuffer = new(*ppBuffer); + //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); + allocatedRegions.Add(pBuffer); + return 0; + }; + getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + { + IExecutionManager eman = target.Contracts.ExecutionManager; + + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + } + }; + + int ret = AMD64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); + + foreach (IntPtr ptr in allocatedRegions) + { + //Console.WriteLine($"Freeing buffer at {ptr:x16}"); + NativeMemory.Free(ptr.ToPointer()); + } + + return ret; + } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e93650f7f353d1..3dccfab0865bb2 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -572,7 +572,10 @@ int ISOSDacInterface.GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) { try { - _target.Contracts.StackWalk.TestEntry(); + if (ip == 1) + { + _target.Contracts.StackWalk.TestEntry(); + } } catch { diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 2af9e9281927c3..bd66f349d59e76 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -30,6 +30,12 @@ + + + + + + From 5d9317afd7afe1136e925bd7eddc1194b3b2a278 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 23 Jan 2025 17:21:49 -0500 Subject: [PATCH 07/63] crossplatform LazyMachState fixes --- .../debug/runtimeinfo/datadescriptor.h | 6 ++---- src/coreclr/vm/amd64/gmscpu.h | 2 -- .../Contracts/StackWalk/FrameIterator.cs | 6 ++++++ .../Data/Frames/LazyMachState.cs | 21 +++++++++++++------ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 18d3aa08bc4f1c..a6955548002fbc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -602,16 +602,14 @@ DEFINE_FRAME_TYPE(HelperMethodFrame_3OBJ) DEFINE_FRAME_TYPE(HelperMethodFrame_PROTECTOBJ) #undef DEFINE_FRAME_TYPE +#ifdef TARGET_AMD64 CDAC_TYPE_BEGIN(LazyMachState) CDAC_TYPE_SIZE(sizeof(LazyMachState)) CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, InstructionPointer, cdac_data::InstructionPointer) CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, StackPointer, cdac_data::StackPointer) CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, ReturnAddress, cdac_data::ReturnAddress) -CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, CapturedInstructionPointer, offsetof(LazyMachState, m_CaptureRip)) -CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, CapturedStackPointer, offsetof(LazyMachState, m_CaptureRsp)) -CDAC_TYPE_FIELD(LazyMachState, /*CalleeSavedRegisters*/, CalleeSavedRegisters, cdac_data::CalleeSavedRegisters) -CDAC_TYPE_FIELD(LazyMachState, /*CalleeSavedRegistersPointers*/, CalleeSavedRegistersPointers, cdac_data::CalleeSavedRegistersPointers) CDAC_TYPE_END(LazyMachState) +#endif // TARGET_AMD64 CDAC_TYPES_END() diff --git a/src/coreclr/vm/amd64/gmscpu.h b/src/coreclr/vm/amd64/gmscpu.h index 13036051767ac4..5f2c483a16dabf 100644 --- a/src/coreclr/vm/amd64/gmscpu.h +++ b/src/coreclr/vm/amd64/gmscpu.h @@ -125,8 +125,6 @@ struct cdac_data static constexpr size_t InstructionPointer = offsetof(LazyMachState, m_Rip); static constexpr size_t StackPointer = offsetof(LazyMachState, m_Rsp); static constexpr size_t ReturnAddress = offsetof(LazyMachState, _pRetAddr); - static constexpr size_t CalleeSavedRegisters = offsetof(LazyMachState, m_Capture); - static constexpr size_t CalleeSavedRegistersPointers = offsetof(LazyMachState, m_Ptrs); }; inline void LazyMachState::setLazyStateFromUnwind(MachState* copy) 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 index f1e0f32e6cf2b1..dd84c581e375cc 100644 --- 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 @@ -39,6 +39,12 @@ public static bool TryGetContext(Target target, Data.Frame frame, [NotNullWhen(t case DataType.HelperMethodFrame_3OBJ: case DataType.HelperMethodFrame_PROTECTOBJ: Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(frame.Address); + if (helperMethodFrame.LazyMachState.StackPointer is null || helperMethodFrame.LazyMachState.InstructionPointer is null) + { + IP = null; + SP = null; + return false; + } IP = helperMethodFrame.LazyMachState.InstructionPointer; SP = helperMethodFrame.LazyMachState.StackPointer; return true; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs index 90b58ab0e6fb69..867ef7890e1aa9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs @@ -14,12 +14,21 @@ static LazyMachState IData.Create(Target target, TargetPointer ad public LazyMachState(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.LazyMachState); - InstructionPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(InstructionPointer)].Offset); - StackPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(StackPointer)].Offset); - ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + if (type.Fields.ContainsKey(nameof(InstructionPointer))) + { + InstructionPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(InstructionPointer)].Offset); + } + if (type.Fields.ContainsKey(nameof(StackPointer))) + { + StackPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(StackPointer)].Offset); + } + if (type.Fields.ContainsKey(nameof(ReturnAddress))) + { + ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + } } - public TargetPointer InstructionPointer { get; } - public TargetPointer StackPointer { get; } - public TargetPointer ReturnAddress { get; } + public TargetPointer? InstructionPointer { get; } + public TargetPointer? StackPointer { get; } + public TargetPointer? ReturnAddress { get; } } From 2a1e410b147f1b462878d087b1f541d189e3b4df Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 24 Jan 2025 18:21:13 -0500 Subject: [PATCH 08/63] add support for building arm64 and amd64 unwinders --- src/coreclr/unwinder/CMakeLists.txt | 108 ++++++++++++--- src/coreclr/unwinder/amd64/unwinder.cpp | 2 + src/coreclr/unwinder/arm64/unwinder.cpp | 38 +++++- src/coreclr/unwinder/arm64/unwinder.h | 10 ++ src/coreclr/unwinder/baseunwinder.cpp | 8 +- .../Contracts/StackWalk/StackWalk_1.cs | 128 ++++++++++++++---- .../managed/cdacreader/src/cdacreader.csproj | 8 +- src/tests/Common/Directory.Build.targets | 4 + 8 files changed, 248 insertions(+), 58 deletions(-) diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index 6eded4eb7b68ba..59e78f36b5163f 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -1,18 +1,8 @@ -include_directories(BEFORE ${VM_DIR}) -include_directories(BEFORE ${VM_DIR}/${ARCH_SOURCES_DIR}) -include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(BEFORE ${CLR_DIR}/unwinder) -include_directories(${CLR_DIR}/debug/ee) -include_directories(${CLR_DIR}/gc) -include_directories(${CLR_DIR}/gcdump) -include_directories(${CLR_DIR}/debug/daccess) - set(UNWINDER_SOURCES baseunwinder.cpp ) # Include platform specific unwinder for applicable (native and cross-target) builds. -include_directories(${ARCH_SOURCES_DIR}) list(APPEND UNWINDER_SOURCES ${ARCH_SOURCES_DIR}/unwinder.cpp ) @@ -22,21 +12,97 @@ convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) if(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_wks OBJECT ${UNWINDER_SOURCES}) add_dependencies(unwinder_wks eventing_headers) + target_include_directories(unwinder_wks BEFORE PRIVATE ${VM_DIR}) + target_include_directories(unwinder_wks BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) + target_include_directories(unwinder_wks BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(unwinder_wks BEFORE PRIVATE ${CLR_DIR}/unwinder) + target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/debug/ee) + target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/gc) + target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/gcdump) + target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/debug/daccess) + target_include_directories(unwinder_wks PRIVATE ${ARCH_SOURCES_DIR}) endif(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_dac ${UNWINDER_SOURCES}) add_dependencies(unwinder_dac eventing_headers) +target_include_directories(unwinder_dac BEFORE PRIVATE ${VM_DIR}) +target_include_directories(unwinder_dac BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) +target_include_directories(unwinder_dac BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(unwinder_dac BEFORE PRIVATE ${CLR_DIR}/unwinder) +target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/debug/ee) +target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/gc) +target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/gcdump) +target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/debug/daccess) +target_include_directories(unwinder_dac PRIVATE ${ARCH_SOURCES_DIR}) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) -add_library_clr(unwinder_cdac SHARED ${UNWINDER_SOURCES}) -add_dependencies(unwinder_cdac eventing_headers) -target_link_libraries(unwinder_cdac PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) -target_compile_definitions(unwinder_cdac PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) -install_clr(TARGETS unwinder_cdac DESTINATIONS cdaclibs COMPONENT debug INSTALL_ALL_ARTIFACTS) - -add_library_clr(unwinder_cdac_static ${UNWINDER_SOURCES}) -add_dependencies(unwinder_cdac_static eventing_headers) -target_link_libraries(unwinder_cdac_static PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) -target_compile_definitions(unwinder_cdac_static PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) -install_clr(TARGETS unwinder_cdac_static DESTINATIONS cdaclibs COMPONENT debug) +# add_library_clr(unwinder_cdac SHARED ${UNWINDER_SOURCES}) +# add_dependencies(unwinder_cdac eventing_headers) +# target_link_libraries(unwinder_cdac PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) +# target_compile_definitions(unwinder_cdac PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +# install_clr(TARGETS unwinder_cdac DESTINATIONS cdaclibs COMPONENT debug INSTALL_ALL_ARTIFACTS) + +# add_library_clr(unwinder_cdac_static ${UNWINDER_SOURCES}) +# add_dependencies(unwinder_cdac_static eventing_headers) +# target_link_libraries(unwinder_cdac_static PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) +# target_compile_definitions(unwinder_cdac_static PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) +# install_clr(TARGETS unwinder_cdac_static DESTINATIONS cdaclibs COMPONENT debug) + +function(create_platform_unwinder) + set(oneValueArgs TARGET ARCH) + set(multiValueArgs DESTINATIONS) + cmake_parse_arguments(TARGETDETAILS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(TARGETDETAILS_ARCH STREQUAL "x64") + set(ARCH_SOURCES_DIR amd64) + elseif((TARGETDETAILS_ARCH STREQUAL "arm") OR (TARGETDETAILS_ARCH STREQUAL "armel")) + set(ARCH_SOURCES_DIR arm) + elseif(TARGETDETAILS_ARCH STREQUAL "x86") + set(ARCH_SOURCES_DIR i386) + elseif(TARGETDETAILS_ARCH STREQUAL "arm64") + set(ARCH_SOURCES_DIR arm64) + else() + clr_unknown_arch() + endif() + + set(UNWINDER_SOURCES + baseunwinder.cpp + ${ARCH_SOURCES_DIR}/unwinder.cpp + ) + + message("${UNWINDER_SOURCES}") + + convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) + + add_library_clr(${TARGETDETAILS_TARGET} + SHARED + ${UNWINDER_SOURCES} + ) + + add_dependencies(${TARGETDETAILS_TARGET} eventing_headers) + + target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${VM_DIR}) + target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) + target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${CLR_DIR}/unwinder) + target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/debug/ee) + target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/gc) + target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/gcdump) + target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/debug/daccess) + target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${ARCH_SOURCES_DIR}) + + target_link_libraries(${TARGETDETAILS_TARGET} PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) + + # add the install targets + install_clr(TARGETS ${TARGETDETAILS_TARGET} DESTINATIONS ${TARGETDETAILS_DESTINATIONS} COMPONENT debug INSTALL_ALL_ARTIFACTS) + + + set_target_definitions_to_custom_os_and_arch(TARGET ${TARGETDETAILS_TARGET} OS win ARCH ${TARGETDETAILS_ARCH}) + + target_compile_definitions(${TARGETDETAILS_TARGET} PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) + # target_compile_definitions(${TARGETDETAILS_TARGET} PRIVATE SELF_NO_HOST) +endfunction() + +create_platform_unwinder(TARGET unwinder_cdac_amd64 ARCH x64 DESTINATIONS cdaclibs) +create_platform_unwinder(TARGET unwinder_cdac_arm64 ARCH arm64 DESTINATIONS cdaclibs) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 85c2e7f30c906e..f9f94cf325d5ab 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -352,6 +352,8 @@ BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer g { HRESULT hr = E_FAIL; + return (long)((CONTEXT*)pContext)->Rip; + OOPStackUnwinderAMD64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; hr = unwinder.Unwind((CONTEXT*) pContext); diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index c7d04a70255fa1..c5cadb94b9cde7 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -4,7 +4,11 @@ // #include "stdafx.h" + +#ifndef FEATURE_CDAC_UNWINDER #include "utilcode.h" +#endif // FEATURE_CDAC_UNWINDER + #include "crosscomp.h" #include "unwinder.h" @@ -22,6 +26,12 @@ #define PRUNTIME_FUNCTION PT_RUNTIME_FUNCTION #endif +#ifndef FEATURE_CDAC_UNWINDER +#define UNWINDER_ASSERT _ASSERTE +#else // !FEATURE_CDAC_UNWINDER +#define UNWINDER_ASSERT(x) +#endif // FEATURE_CDAC_UNWINDER + #ifndef __in #define __in _In_ #define __out _Out_ @@ -823,7 +833,7 @@ RtlpExpandCompactToFull ( // !sav_predec_done can't even happen. // - _ASSERTE(sav_predec_done); + UNWINDER_ASSERT(sav_predec_done); DBG_OP("save_lrpair\t(%s, %i)\n", int_reg_names[intreg], sav_slot * 8); emit_save_lrpair(&op_buffer, intreg, sav_slot * 8); @@ -1063,7 +1073,7 @@ Return Value: --*/ { - _ASSERTE(UnwindCode <= 0xFF); + UNWINDER_ASSERT(UnwindCode <= 0xFF); if (UnwindCode < 0xC0) { if (ARGUMENT_PRESENT(ScopeSize)) { @@ -1621,7 +1631,7 @@ Return Value: case 0xeb: // MSFT_OP_EC_CONTEXT: // NOTE: for .NET, the arm64ec context restoring is not implemented - _ASSERTE(FALSE); + UNWINDER_ASSERT(FALSE); return STATUS_UNSUCCESSFUL; case 0xec: // MSFT_OP_CLEAR_UNWOUND_TO_CALL @@ -2335,7 +2345,7 @@ Return Value: } // - // pac (11111100): function has pointer authentication + // pac (11111100): function has pointer authentication // else if (CurCode == 0xfc) { @@ -2574,7 +2584,7 @@ Return Value: UNREFERENCED_PARAMETER(HandlerType); - _ASSERTE((UnwindFlags & ~RTL_VIRTUAL_UNWIND_VALID_FLAGS_ARM64) == 0); + UNWINDER_ASSERT((UnwindFlags & ~RTL_VIRTUAL_UNWIND_VALID_FLAGS_ARM64) == 0); if (FunctionEntry == NULL) { @@ -2648,7 +2658,7 @@ Return Value: FunctionEntry = (PRUNTIME_FUNCTION)(ImageBase + FunctionEntry->UnwindData - 3); UnwindType = (FunctionEntry->UnwindData & 3); - _ASSERTE(UnwindType != 3); + UNWINDER_ASSERT(UnwindType != 3); ControlPcRva = FunctionEntry->BeginAddress; @@ -2759,6 +2769,7 @@ BOOL OOPStackUnwinderArm64::Unwind(T_CONTEXT * pContext) return TRUE; } +#ifdef DACCESS_COMPILE BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* pContextPointers) { OOPStackUnwinderArm64 unwinder; @@ -2774,6 +2785,21 @@ BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* p return res; } +#endif // DACCESS_COMPILE + +#ifdef FEATURE_CDAC_UNWINDER +BOOL arm64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) +{ + HRESULT hr = E_FAIL; + + return (long)((T_CONTEXT*)pContext)->Pc; + + OOPStackUnwinderArm64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; + hr = unwinder.Unwind((T_CONTEXT*) pContext); + + return (hr == S_OK); +} +#endif #if defined(HOST_UNIX) diff --git a/src/coreclr/unwinder/arm64/unwinder.h b/src/coreclr/unwinder/arm64/unwinder.h index aa03c5a59fe9f1..3532e624b4b0f6 100644 --- a/src/coreclr/unwinder/arm64/unwinder.h +++ b/src/coreclr/unwinder/arm64/unwinder.h @@ -8,6 +8,9 @@ #include "baseunwinder.h" +#ifdef FEATURE_CDAC_UNWINDER +EXTERN_C __declspec(dllexport) BOOL arm64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo); +#endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // @@ -16,6 +19,13 @@ class OOPStackUnwinderArm64 : public OOPStackUnwinder { +#ifdef FEATURE_CDAC_UNWINDER +public: + OOPStackUnwinderArm64(ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) + : OOPStackUnwinder(readCallback, getAllocatedBuffer, getStackWalkInfo) + { } +#endif // FEATURE_CDAC_UNWINDER + public: // Unwind the given CONTEXT to the caller CONTEXT. The CONTEXT will be overwritten. BOOL Unwind(T_CONTEXT * pContext); diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index 36c4c6cab67389..2087867d4c29a9 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -56,10 +56,10 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres _Out_writes_(cbBuffer) PVOID pBuffer, DWORD cbBuffer) { - if (cbBuffer < sizeof(T_RUNTIME_FUNCTION)) - { - return E_INVALIDARG; - } + // if (cbBuffer < sizeof(T_RUNTIME_FUNCTION)) + // { + // return E_INVALIDARG; + // } PVOID pFuncEntry = NULL; #ifndef FEATURE_CDAC_UNWINDER 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 3071352e225356..f92fb778a7a780 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 @@ -51,23 +51,43 @@ void IStackWalk.TestEntry() using StreamWriter writer = new StreamWriter(outputhPath); Console.SetOut(writer); - ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); - ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); + AMD64Context amd64Context = new AMD64Context() + { + Rsp = 0x0000000000000000, + Rip = 0x1, + }; + ARM64Context arm64Context = new ARM64Context() + { + Sp = 0x0000000000000000, + Pc = 0x2, + }; + try + { + Console.WriteLine(Unwinder.AMD64Unwind(ref amd64Context, _target)); + Console.WriteLine(Unwinder.ARM64Unwind(ref arm64Context, _target)); + } + catch (System.Exception ex) + { + Console.WriteLine(ex); + } - IExecutionManager eman = _target.Contracts.ExecutionManager; + // ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); + // ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); - AMD64Context context = GetThreadContext(threadData); - Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - CheckIP(new(context.InstructionPointer.Value)); + // IExecutionManager eman = _target.Contracts.ExecutionManager; - foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) - { - FrameIterator.PrintFrame(_target, frame); - if (FrameIterator.TryGetContext(_target, frame, out TargetPointer? IP, out TargetPointer? SP)) - { - UnwindUntilNative(IP.Value, SP.Value); - } - } + // AMD64Context context = GetThreadContext(threadData); + // Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + // CheckIP(new(context.InstructionPointer.Value)); + + // foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) + // { + // FrameIterator.PrintFrame(_target, frame); + // if (FrameIterator.TryGetContext(_target, frame, out TargetPointer? IP, out TargetPointer? SP)) + // { + // UnwindUntilNative(IP.Value, SP.Value); + // } + // } writer.Flush(); } @@ -100,12 +120,10 @@ private void UnwindUntilNative(TargetPointer ip, TargetPointer sp) while (eman.GetCodeBlockHandle(new(context.Rip)) is CodeBlockHandle cbh) { TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, new(context.Rip)); Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16} MethodDesc: {methodDesc.Value:x16}"); try { - Unwinder.AMD64Unwind(ref context, moduleBase.Value, new IntPtr((long)unwindInfo.Value), _target); + Unwinder.AMD64Unwind(ref context, _target); } catch (System.Exception ex) { @@ -136,7 +154,7 @@ private void Unwind(TargetPointer ip, TargetPointer sp) Rip = ip, }; Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - Unwinder.AMD64Unwind(ref context, moduleBase.Value, new IntPtr((long)unwindInfo.Value), _target); + Unwinder.AMD64Unwind(ref context, _target); Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); } catch (System.Exception ex) @@ -153,9 +171,6 @@ private void Unwind(TargetPointer ip, TargetPointer sp) internal static unsafe partial class Unwinder { - [LibraryImport("unwinder_cdac", EntryPoint = "timesTwo")] - public static partial int TimesTwo(int x); - // ReadCallback allows the unwinder to read memory from the target process // into an allocated buffer. This buffer is either allocated by the unwinder // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. @@ -169,7 +184,14 @@ internal static unsafe partial class Unwinder // If the moduleBase or funcEntry can not be found, both will be 0. public delegate void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry); - [LibraryImport("unwinder_cdac", EntryPoint = "amd64Unwind")] + [LibraryImport("unwinder_cdac_arm64", EntryPoint = "arm64Unwind")] + private static partial int ARM64Unwind( + ref ARM64Context context, + [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, + [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, + [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + + [LibraryImport("unwinder_cdac_amd64", EntryPoint = "amd64Unwind")] private static partial int AMD64Unwind( ref AMD64Context context, [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, @@ -178,8 +200,6 @@ private static partial int AMD64Unwind( public static int AMD64Unwind( ref AMD64Context context, - ulong moduleBase, - IntPtr functionEntry, Target target) { ReadCallback readCallback; @@ -237,4 +257,64 @@ public static int AMD64Unwind( return ret; } + + public static int ARM64Unwind( + ref ARM64Context context, + Target target) + { + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + + // Move to IDisposable for freeing + List allocatedRegions = []; + + readCallback = (address, pBuffer, bufferSize) => + { + Span span = new Span(pBuffer, bufferSize); + target.ReadBuffer(address, span); + return 0; + }; + getAllocatedBuffer = (bufferSize, ppBuffer) => + { + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + IntPtr pBuffer = new(*ppBuffer); + //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); + allocatedRegions.Add(pBuffer); + return 0; + }; + getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + { + IExecutionManager eman = target.Contracts.ExecutionManager; + + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + } + }; + + int ret = ARM64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); + + foreach (IntPtr ptr in allocatedRegions) + { + //Console.WriteLine($"Freeing buffer at {ptr:x16}"); + NativeMemory.Free(ptr.ToPointer()); + } + + return ret; + } } diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index bd66f349d59e76..eb4549410a782c 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -31,9 +31,11 @@ - - - + + + + + diff --git a/src/tests/Common/Directory.Build.targets b/src/tests/Common/Directory.Build.targets index ca3cd3786b9713..3a7cf329b59df5 100644 --- a/src/tests/Common/Directory.Build.targets +++ b/src/tests/Common/Directory.Build.targets @@ -122,6 +122,10 @@ True + + True + + From da73082a174d3fe903e2fc45ba13f78ffaa40f62 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 24 Jan 2025 18:26:03 -0500 Subject: [PATCH 09/63] add GetPlatform --- src/coreclr/debug/daccess/cdac.cpp | 8 +++++- .../Target.cs | 1 + .../Contracts/StackWalk/StackWalk_1.cs | 9 ++++-- .../ContractDescriptorTarget.cs | 28 +++++++++++++++++-- .../managed/cdacreader/inc/cdac_reader.h | 1 + .../managed/cdacreader/src/Entrypoints.cs | 8 ++++++ .../ContractDescriptorBuilder.cs | 2 +- .../cdacreader/tests/TestPlaceholderTarget.cs | 2 ++ 8 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index 947b8b9d9a26e5..cd2046cbf22bf4 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -51,6 +51,12 @@ namespace return S_OK; } + + int GetPlatform(uint32_t* platform, void* context) + { + ICorDebugDataTarget* target = reinterpret_cast(context); + return target->GetPlatform((CorDebugPlatform*)platform); + } } CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl) @@ -63,7 +69,7 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown _ASSERTE(init != nullptr); intptr_t handle; - if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, target, &handle) != 0) + if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, &GetPlatform, target, &handle) != 0) { ::FreeLibrary(cdacLib); return {}; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index bef88a93f361fe..af32332d90dd87 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -28,6 +28,7 @@ internal abstract class Target public abstract bool IsLittleEndian { get; } public abstract int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); + public abstract int GetPlatform(out int platform); /// /// Reads a well-known global pointer value from the target process 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 f92fb778a7a780..cf502822a670b5 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 @@ -71,10 +71,13 @@ void IStackWalk.TestEntry() Console.WriteLine(ex); } - // ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); - // ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); + ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); + ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); - // IExecutionManager eman = _target.Contracts.ExecutionManager; + IExecutionManager eman = _target.Contracts.ExecutionManager; + + _target.GetPlatform(out int platform); + Console.WriteLine($"Platform: {platform}"); // AMD64Context context = GetThreadContext(threadData); // Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 700047ebd37917..6367b4ed23ce34 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -44,16 +44,29 @@ private readonly struct Configuration public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); + public delegate int GetTargetPlatform(out int platform); public GetTargetThreadContextDelegate? getThreadContext; - - public static bool TryCreate(ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, out ContractDescriptorTarget? target) + public GetTargetPlatform? getTargetPlatform; + + public static bool TryCreate( + ulong contractDescriptor, + ReadFromTargetDelegate readFromTarget, + GetTargetThreadContextDelegate getThreadContext, + GetTargetPlatform getTargetPlatform, + out ContractDescriptorTarget? target) { Reader reader = new Reader(readFromTarget); - if (TryReadContractDescriptor(contractDescriptor, reader, out Configuration config, out ContractDescriptorParser.ContractDescriptor? descriptor, out TargetPointer[] pointerData)) + if (TryReadContractDescriptor( + contractDescriptor, + reader, + out Configuration config, + out ContractDescriptorParser.ContractDescriptor? descriptor, + out TargetPointer[] pointerData)) { target = new ContractDescriptorTarget(config, descriptor!, pointerData, reader); target.getThreadContext = getThreadContext; + target.getTargetPlatform = getTargetPlatform; return true; } @@ -226,6 +239,15 @@ public override int GetThreadContext(uint threadId, uint contextFlags, uint cont return hr; } + public override int GetPlatform(out int platform) + { + if (getTargetPlatform is null) + throw new InvalidOperationException("GetTargetPlatform is not available"); + + int hr = getTargetPlatform(out platform); + return hr; + } + /// /// Read a value from the target in target endianness /// diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 6597a9dbd70178..3e4bf5806ca074 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -19,6 +19,7 @@ int cdac_reader_init( uint64_t descriptor, int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), int(*read_thread_context)(uint32_t, uint32_t, uint32_t, uint8_t*, void*), + int(*get_platform)(uint32_t*, void*), void* read_context, /*out*/ intptr_t* handle); diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index e2df262d9b1993..015247efc0a9c4 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -16,6 +16,7 @@ private static unsafe int Init( ulong descriptor, delegate* unmanaged readFromTarget, delegate* unmanaged readThreadContext, + delegate* unmanaged getPlatform, void* readContext, IntPtr* handle) { @@ -36,6 +37,13 @@ private static unsafe int Init( return readThreadContext(threadId, contextFlags, contextSize, bufferPtr, readContext); } }, + (out int platform) => + { + fixed (int* platformPtr = &platform) + { + return getPlatform(platformPtr, readContext); + } + }, out ContractDescriptorTarget? target)) return -1; diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index 4c9fdef137037f..231af072fcc597 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -168,6 +168,6 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta throw new InvalidOperationException("Context already created"); ulong contractDescriptorAddress = CreateDescriptorFragments(); MockMemorySpace.ReadContext context = GetReadContext(); - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, out target); + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, null, out target); } } diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 10e977d34e8d15..c4564c0c453047 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -231,6 +231,8 @@ public override int GetThreadContext(uint threadId, uint contextFlags, uint cont throw new InvalidOperationException("GetThreadContext is not available"); } + public override int GetPlatform(out int platform) => throw new NotImplementedException(); + public override Target.IDataCache ProcessedData => _dataCache; public override ContractRegistry Contracts => _contractRegistry; From a794374f0369594b04aea251e5c312625c00426d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 28 Jan 2025 14:31:57 -0500 Subject: [PATCH 10/63] AMD64/ARM64 stackwalking backend --- .../debug/runtimeinfo/datadescriptor.h | 8 + src/coreclr/unwinder/amd64/unwinder.cpp | 2 - src/coreclr/unwinder/arm64/unwinder.cpp | 20 +- src/coreclr/unwinder/baseunwinder.h | 4 + src/coreclr/vm/frames.h | 28 +- .../Target.cs | 20 +- .../ExecutionManagerBase.EEJitManager.cs | 21 +- .../Contracts/StackWalk/AMD64Context.cs | 45 ++- .../Contracts/StackWalk/ARM64Context.cs | 71 ++++- .../Contracts/StackWalk/FrameIterator.cs | 42 ++- .../Contracts/StackWalk/IContext.cs | 34 +- .../Contracts/StackWalk/StackWalk_1.cs | 291 ++++-------------- .../Contracts/StackWalk/Unwinder.cs | 158 ++++++++++ .../Data/Frames/Frame.cs | 2 +- .../Data/Frames/SoftwareExceptionFrame.cs | 22 ++ .../ContractDescriptorTarget.cs | 6 +- .../cdacreader/tests/TestPlaceholderTarget.cs | 2 +- 17 files changed, 498 insertions(+), 278 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index a6955548002fbc..14ac85da7f0873 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -587,6 +587,14 @@ CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CallerReturnAddress, offsetof(Inl CDAC_TYPE_FIELD(InlinedCallFrame, /*pointer*/, CalleeSavedFP, offsetof(InlinedCallFrame, m_pCalleeSavedFP)) CDAC_TYPE_END(InlinedCallFrame) +#ifdef FEATURE_EH_FUNCLETS +CDAC_TYPE_BEGIN(SoftwareExceptionFrame) +CDAC_TYPE_SIZE(sizeof(SoftwareExceptionFrame)) +CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data::TargetContext) +CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*pointer*/, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_END(SoftwareExceptionFrame) +#endif // FEATURE_EH_FUNCLETS + #define DEFINE_FRAME_TYPE(frameType) \ CDAC_TYPE_BEGIN(frameType) \ CDAC_TYPE_SIZE(sizeof(frameType)) \ diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index f9f94cf325d5ab..85c2e7f30c906e 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -352,8 +352,6 @@ BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer g { HRESULT hr = E_FAIL; - return (long)((CONTEXT*)pContext)->Rip; - OOPStackUnwinderAMD64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; hr = unwinder.Unwind((CONTEXT*) pContext); diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index c5cadb94b9cde7..2260bfeb9458d5 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -174,13 +174,25 @@ typedef struct _ARM64_VFP_STATE // Macros for accessing memory. These can be overridden if other code // (in particular the debugger) needs to use them. -#if !defined(DEBUGGER_UNWIND) +#if !defined(DEBUGGER_UNWIND) && !defined(FEATURE_CDAC_UNWINDER) #define MEMORY_READ_BYTE(params, addr) (*dac_cast(addr)) #define MEMORY_READ_WORD(params, addr) (*dac_cast(addr)) #define MEMORY_READ_DWORD(params, addr) (*dac_cast(addr)) #define MEMORY_READ_QWORD(params, addr) (*dac_cast(addr)) +#elif defined(FEATURE_CDAC_UNWINDER) +template +T cdacRead(uint64_t addr) +{ + T t; + g_pUnwinder->readCallback(addr, &t, sizeof(t)); + return t; +} +#define MEMORY_READ_BYTE(params, addr) (cdacRead(addr)) +#define MEMORY_READ_WORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_DWORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_QWORD(params, addr) (cdacRead(addr)) #endif // @@ -2788,15 +2800,17 @@ BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* p #endif // DACCESS_COMPILE #ifdef FEATURE_CDAC_UNWINDER +OOPStackUnwinderArm64* g_pUnwinder; BOOL arm64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) { HRESULT hr = E_FAIL; - return (long)((T_CONTEXT*)pContext)->Pc; - OOPStackUnwinderArm64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; + g_pUnwinder = &unwinder; hr = unwinder.Unwind((T_CONTEXT*) pContext); + g_pUnwinder = nullptr; + return (hr == S_OK); } #endif diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 2411217e3a50a4..1bd9ab5845d4a1 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -52,6 +52,10 @@ class OOPStackUnwinder getStackWalkInfo(getStackWalkInfo) { } + +public: + // These functions are marked public because they are called using + // a global instance of OOPStackUnwinder in the ARM64 implementation. ReadCallback readCallback; GetAllocatedBuffer getAllocatedBuffer; GetStackWalkInfo getStackWalkInfo; diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 7312cf69ed9484..2bdb441723af3d 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -1213,10 +1213,18 @@ class SoftwareExceptionFrame : public Frame virtual void UpdateRegDisplay(const PREGDISPLAY, bool updateFloats = false); + friend struct ::cdac_data; + // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_DTOR(SoftwareExceptionFrame) }; +template<> +struct cdac_data +{ + static constexpr size_t TargetContext = offsetof(SoftwareExceptionFrame, m_Context); + static constexpr size_t ReturnAddress = offsetof(SoftwareExceptionFrame, m_ReturnAddress); +}; #endif // FEATURE_EH_FUNCLETS //----------------------------------------------------------------------- @@ -1452,10 +1460,10 @@ class HelperMethodFrame : public Frame LazyMachState m_MachState; // pRetAddr points to the return address and the stack arguments + friend struct ::cdac_data; + // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame) - - friend struct ::cdac_data; }; template<> @@ -1547,10 +1555,10 @@ class HelperMethodFrame_1OBJ : public HelperMethodFrame private: PTR_OBJECTREF gcPtrs[1]; + friend struct ::cdac_data; + // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_1OBJ) - - friend struct ::cdac_data; }; template<> @@ -1618,10 +1626,10 @@ class HelperMethodFrame_2OBJ : public HelperMethodFrame private: PTR_OBJECTREF gcPtrs[2]; + friend struct ::cdac_data; + // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_2OBJ) - - friend struct ::cdac_data; }; template<> @@ -1694,10 +1702,10 @@ class HelperMethodFrame_3OBJ : public HelperMethodFrame private: PTR_OBJECTREF gcPtrs[3]; + friend struct ::cdac_data; + // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_3OBJ) - - friend struct ::cdac_data; }; template<> @@ -1770,10 +1778,10 @@ class HelperMethodFrame_PROTECTOBJ : public HelperMethodFrame PTR_OBJECTREF m_pObjRefs; UINT m_numObjRefs; + friend struct ::cdac_data; + // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_PROTECTOBJ) - - friend struct ::cdac_data; }; template<> diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index af32332d90dd87..354c419666ceb9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -18,6 +18,24 @@ namespace Microsoft.Diagnostics.DataContractReader; /// internal abstract class Target { + public enum CorDebugPlatform : int + { + CORDB_PLATFORM_WINDOWS_X86 = 0, + CORDB_PLATFORM_WINDOWS_AMD64 = 1, + CORDB_PLATFORM_WINDOWS_IA64 = 2, + CORDB_PLATFORM_MAC_PPC = 3, + CORDB_PLATFORM_MAC_X86 = 4, + CORDB_PLATFORM_WINDOWS_ARM = 5, + CORDB_PLATFORM_MAC_AMD64 = 6, + CORDB_PLATFORM_WINDOWS_ARM64 = 7, + CORDB_PLATFORM_POSIX_AMD64 = 8, + CORDB_PLATFORM_POSIX_X86 = 9, + CORDB_PLATFORM_POSIX_ARM = 10, + CORDB_PLATFORM_POSIX_ARM64 = 11, + CORDB_PLATFORM_POSIX_LOONGARCH64 = 12, + CORDB_PLATFORM_POSIX_RISCV64 = 12 + } + /// /// Pointer size of the target /// @@ -28,7 +46,7 @@ internal abstract class Target public abstract bool IsLittleEndian { get; } public abstract int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); - public abstract int GetPlatform(out int platform); + public abstract int GetPlatform(out CorDebugPlatform platform); /// /// Reads a well-known global pointer value from the target process diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs index 7246add7e9ad2f..bb81a4cb68df3b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs @@ -71,19 +71,26 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod throw new InvalidOperationException("Unable to get NumUnwindInfos"); } - ulong imageBase = rangeSection.Data.RangeBegin; + if (numUnwindInfos == 0) + { + return TargetPointer.Null; + } - for (ulong i = 0; i < numUnwindInfos; i++) + ulong imageBase = rangeSection.Data.RangeBegin; + TargetPointer prevUnwindInfoAddress = unwindInfos; + TargetPointer currUnwindInfoAddress; + for (ulong i = 1; i < numUnwindInfos; i++) { - TargetPointer unwindInfoAddress = unwindInfos + (i * runtimeFunctionSize); - Data.RuntimeFunction runtimeFunction = Target.ProcessedData.GetOrAdd(unwindInfoAddress); - if (runtimeFunction.BeginAddress + imageBase <= jittedCodeAddress.Value && runtimeFunction.EndAddress + imageBase >= jittedCodeAddress.Value) + currUnwindInfoAddress = unwindInfos + (i * runtimeFunctionSize); + Data.RuntimeFunction nextRuntimeFunction = Target.ProcessedData.GetOrAdd(currUnwindInfoAddress); + if (nextRuntimeFunction.BeginAddress + imageBase > jittedCodeAddress.Value) { - return unwindInfoAddress; + return prevUnwindInfoAddress; } + prevUnwindInfoAddress = currUnwindInfoAddress; } - return TargetPointer.Null; + return prevUnwindInfoAddress; } private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs index 91d741d599a4ec..58f9a4320b1aed 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// AMD64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -public struct AMD64Context : IContext +internal struct AMD64Context : IContext { [Flags] public enum ContextFlagsValues : uint @@ -32,8 +32,47 @@ public enum ContextFlagsValues : uint public static uint Size => 0x4d0; public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; - public readonly TargetPointer StackPointer => new(Rsp); - public readonly TargetPointer InstructionPointer => new(Rip); + public TargetPointer StackPointer + { + readonly get => new(Rsp); + set => Rsp = value.Value; + } + public TargetPointer InstructionPointer + { + readonly get => new(Rip); + set => Rip = value.Value; + } + public TargetPointer FramePointer + { + readonly get => new(Rbp); + set => Rbp = value.Value; + } + + public void Unwind(Target target) + { + Unwinder.AMD64Unwind(ref this, target); + } + + public unsafe void ReadFromAddress(Target target, TargetPointer address) + { + Span buffer = new byte[Size]; + target.ReadBuffer(address, buffer); + Span structSpan = MemoryMarshal.CreateSpan(ref this, sizeof(AMD64Context)); + Span byteSpan = MemoryMarshal.Cast(structSpan); + if (buffer.Length > sizeof(AMD64Context)) + { + buffer.Slice(0, sizeof(AMD64Context)).CopyTo(byteSpan); + } + else + { + buffer.CopyTo(byteSpan); + } + } + + public void Clear() + { + this = default; + } public override string ToString() { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs index bd9f20ddc81bfa..9295ddec48e0a0 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Reflection; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -10,7 +12,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// ARM64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -public struct ARM64Context : IContext +internal struct ARM64Context : IContext { [Flags] public enum ContextFlagsValues : uint @@ -29,8 +31,71 @@ public enum ContextFlagsValues : uint public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; - public readonly TargetPointer StackPointer => new(Sp); - public readonly TargetPointer InstructionPointer => new(Pc); + public TargetPointer StackPointer + { + readonly get => new(Sp); + set => Sp = value.Value; + } + public TargetPointer InstructionPointer + { + readonly get => new(Pc); + set => Pc = value.Value; + } + public TargetPointer FramePointer + { + readonly get => new(Fp); + set => Fp = value.Value; + } + + public void Unwind(Target target) + { + Unwinder.ARM64Unwind(ref this, target); + } + + public void Clear() + { + this = default; + } + + public unsafe void ReadFromAddress(Target target, TargetPointer address) + { + Span buffer = new byte[Size]; + target.ReadBuffer(address, buffer); + Span structSpan = MemoryMarshal.CreateSpan(ref this, sizeof(ARM64Context)); + Span byteSpan = MemoryMarshal.Cast(structSpan); + if (buffer.Length > sizeof(ARM64Context)) + { + buffer.Slice(0, sizeof(ARM64Context)).CopyTo(byteSpan); + } + else + { + buffer.CopyTo(byteSpan); + } + } + + public override string ToString() + { + StringBuilder sb = new(); + foreach (FieldInfo fieldInfo in typeof(AMD64Context).GetFields()) + { + switch (fieldInfo.GetValue(this)) + { + case ulong v: + sb.AppendLine($"{fieldInfo.Name} = {v:x16}"); + break; + case uint v: + sb.AppendLine($"{fieldInfo.Name} = {v:x8}"); + break; + case ushort v: + sb.AppendLine($"{fieldInfo.Name} = {v:x4}"); + break; + default: + sb.AppendLine($"{fieldInfo.Name} = {fieldInfo.GetValue(this)}"); + continue; + } + } + return sb.ToString(); + } // Control flags 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 index dd84c581e375cc..1b19d5a4513aa1 100644 --- 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 @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -23,15 +24,20 @@ internal sealed class FrameIterator framePointer = frame.Next; } } - - public static bool TryGetContext(Target target, Data.Frame frame, [NotNullWhen(true)] out TargetPointer? IP, [NotNullWhen(true)] out TargetPointer? SP) + public static bool TryUpdateContext(Target target, Data.Frame frame, ref IContext context) { switch (frame.Type) { case DataType.InlinedCallFrame: Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); - IP = inlinedCallFrame.CallerReturnAddress; - SP = inlinedCallFrame.CallSiteSP; + 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(frame.Address); + context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); return true; case DataType.HelperMethodFrame: case DataType.HelperMethodFrame_1OBJ: @@ -41,16 +47,19 @@ public static bool TryGetContext(Target target, Data.Frame frame, [NotNullWhen(t Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(frame.Address); if (helperMethodFrame.LazyMachState.StackPointer is null || helperMethodFrame.LazyMachState.InstructionPointer is null) { - IP = null; - SP = null; return false; } - IP = helperMethodFrame.LazyMachState.InstructionPointer; - SP = helperMethodFrame.LazyMachState.StackPointer; + context.Clear(); + if (helperMethodFrame.LazyMachState.InstructionPointer is TargetPointer ip) + { + context.InstructionPointer = ip; + } + if (helperMethodFrame.LazyMachState.StackPointer is TargetPointer sp) + { + context.StackPointer = sp; + } return true; default: - IP = null; - SP = null; Console.WriteLine($"Unable to parse frame further: {frame.Type}"); break; } @@ -65,6 +74,10 @@ public static void PrintFrame(Target target, Data.Frame frame) Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); Print(inlinedCallFrame); break; + case DataType.SoftwareExceptionFrame: + Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(frame.Address); + Print(target, softwareExceptionFrame); + break; case DataType.HelperMethodFrame: case DataType.HelperMethodFrame_1OBJ: case DataType.HelperMethodFrame_2OBJ: @@ -81,7 +94,14 @@ public static void PrintFrame(Target target, Data.Frame frame) public static void Print(InlinedCallFrame inlinedCallFrame) { - Console.WriteLine($"[InlinedCallFrame: IP={inlinedCallFrame.CallerReturnAddress}, SP={inlinedCallFrame.CallSiteSP}]"); + Console.WriteLine($"[InlinedCallFrame: IP={inlinedCallFrame.CallerReturnAddress}, SP={inlinedCallFrame.CallSiteSP}, FP={inlinedCallFrame.CalleeSavedFP}]"); + } + + public static void Print(Target target, SoftwareExceptionFrame softwareExceptionFrame) + { + IContext context = IContext.GetContextForPlatform(target); + context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); + Console.WriteLine($"[SoftwareExceptionFrame: IP={context.InstructionPointer.Value:x16}, SP={context.StackPointer.Value:x16}, FP={context.FramePointer.Value:x16}]"); } public static void Print(HelperMethodFrame helperMethodFrame) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs index 045d8fc48fcfa4..d88ad262336fe2 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs @@ -6,11 +6,35 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -public interface IContext +internal interface IContext { - public static uint Size { get; } - public static uint DefaultContextFlags { get; } + public static abstract uint Size { get; } + public static abstract uint DefaultContextFlags { get; } - public TargetPointer StackPointer { get; } - public TargetPointer InstructionPointer { get; } + public TargetPointer StackPointer { get; set; } + public TargetPointer InstructionPointer { get; set; } + public TargetPointer FramePointer { get; set; } + + public abstract void Clear(); + public abstract void ReadFromAddress(Target target, TargetPointer address); + public abstract void Unwind(Target target); + + public static IContext GetContextForPlatform(Target target) + { + target.GetPlatform(out Target.CorDebugPlatform platform); + switch (platform) + { + case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: + case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: + case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: + AMD64Context amd64Context = default; + return amd64Context; + case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: + case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: + ARM64Context arm64Context = default; + return arm64Context; + default: + throw new ArgumentOutOfRangeException(nameof(platform), platform, null); + } + } } 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 cf502822a670b5..5da16ce49acdb2 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 @@ -21,26 +21,24 @@ internal StackWalk_1(Target target) _target = target; } - private AMD64Context GetThreadContext(ThreadData threadData) + private T GetThreadContext(ThreadData threadData) + where T : struct, IContext { int hr; - unsafe { - byte[] bytes = new byte[AMD64Context.Size]; - - fixed (byte* ptr = bytes) - { - Span buffer = bytes; - hr = _target.GetThreadContext((uint)threadData.OSId.Value, AMD64Context.DefaultContextFlags, AMD64Context.Size, buffer); - } - + byte[] bytes = new byte[T.Size]; + Span buffer = new Span(bytes); + hr = _target.GetThreadContext((uint)threadData.OSId.Value, T.DefaultContextFlags, (uint)T.Size, buffer); if (hr != 0) { throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); } - AMD64Context context = Marshal.PtrToStructure((IntPtr)Unsafe.AsPointer(ref bytes[0])); + T context = default; + Span structSpan = MemoryMarshal.CreateSpan(ref context, sizeof(T)); + Span byteSpan = MemoryMarshal.Cast(structSpan); + buffer.Slice(0, sizeof(T)).CopyTo(byteSpan); return context; } } @@ -51,273 +49,110 @@ void IStackWalk.TestEntry() using StreamWriter writer = new StreamWriter(outputhPath); Console.SetOut(writer); - AMD64Context amd64Context = new AMD64Context() - { - Rsp = 0x0000000000000000, - Rip = 0x1, - }; - ARM64Context arm64Context = new ARM64Context() - { - Sp = 0x0000000000000000, - Pc = 0x2, - }; try { - Console.WriteLine(Unwinder.AMD64Unwind(ref amd64Context, _target)); - Console.WriteLine(Unwinder.ARM64Unwind(ref arm64Context, _target)); + Handle(); } catch (System.Exception ex) { Console.WriteLine(ex); } - - ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); - ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); - - IExecutionManager eman = _target.Contracts.ExecutionManager; - - _target.GetPlatform(out int platform); - Console.WriteLine($"Platform: {platform}"); - - // AMD64Context context = GetThreadContext(threadData); - // Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - // CheckIP(new(context.InstructionPointer.Value)); - - // foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) - // { - // FrameIterator.PrintFrame(_target, frame); - // if (FrameIterator.TryGetContext(_target, frame, out TargetPointer? IP, out TargetPointer? SP)) - // { - // UnwindUntilNative(IP.Value, SP.Value); - // } - // } - - writer.Flush(); - } - - private void CheckIP(TargetCodePointer ip) - { - IExecutionManager eman = _target.Contracts.ExecutionManager; - if (eman.GetCodeBlockHandle(ip) is CodeBlockHandle cbh) - { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, ip); - Console.WriteLine($"MethodDesc: {methodDesc.Value:x16} BaseAddress: {moduleBase.Value:x16} UnwindInfo: {unwindInfo.Value:x16}"); - } - else + finally { - Console.WriteLine("IP is unmanaged"); + writer.Close(); } } - private void UnwindUntilNative(TargetPointer ip, TargetPointer sp) + private void Handle() { - IExecutionManager eman = _target.Contracts.ExecutionManager; + ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); + ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); - AMD64Context context = new AMD64Context() - { - Rsp = sp, - Rip = ip, - }; - while (eman.GetCodeBlockHandle(new(context.Rip)) is CodeBlockHandle cbh) + _target.GetPlatform(out Target.CorDebugPlatform platform); + Console.WriteLine(platform.ToString()); + try { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16} MethodDesc: {methodDesc.Value:x16}"); - try + IContext context; + switch (platform) { - Unwinder.AMD64Unwind(ref context, _target); - } - catch (System.Exception ex) - { - Console.WriteLine(ex); - throw; + case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: + case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: + case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: + context = GetThreadContext(threadData); + Console.WriteLine($"[{context.GetType().Name}: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + Unwind(threadData); + break; + case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: + case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: + context = GetThreadContext(threadData); + Console.WriteLine($"[{context.GetType().Name}: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); + Unwind(threadData); + break; + default: + throw new ArgumentOutOfRangeException(nameof(platform), platform, null); } } - - Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16}"); - Console.WriteLine($"IP is unmanaged, finishing unwind started at {sp.Value:x16}"); + catch (System.Exception ex) + { + Console.WriteLine(ex); + } } - private void Unwind(TargetPointer ip, TargetPointer sp) + private void CheckIP(TargetCodePointer ip) { IExecutionManager eman = _target.Contracts.ExecutionManager; - - if (eman.GetCodeBlockHandle(new(ip.Value)) is CodeBlockHandle cbh) + if (eman.GetCodeBlockHandle(ip) is CodeBlockHandle cbh) { TargetPointer methodDesc = eman.GetMethodDesc(cbh); TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, new(ip.Value)); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, ip); Console.WriteLine($"MethodDesc: {methodDesc.Value:x16} BaseAddress: {moduleBase.Value:x16} UnwindInfo: {unwindInfo.Value:x16}"); - try - { - AMD64Context context = new AMD64Context() - { - Rsp = sp, - Rip = ip, - }; - Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - Unwinder.AMD64Unwind(ref context, _target); - Console.WriteLine($"[AMD64Context: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - } - catch (System.Exception ex) - { - Console.WriteLine(ex); - } } else { Console.WriteLine("IP is unmanaged"); } } -}; - -internal static unsafe partial class Unwinder -{ - // ReadCallback allows the unwinder to read memory from the target process - // into an allocated buffer. This buffer is either allocated by the unwinder - // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. - // In the latter case, the unwinder can only use the buffer for the duration of the - // unwind call. Once the call is over the cDAC will free all allocated buffers. - public delegate int ReadCallback(ulong address, void* buffer, int bufferSize); - public delegate int GetAllocatedBuffer(int bufferSize, void** buffer); - - // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp - // To maintain the same signature as the original function, we return void. - // If the moduleBase or funcEntry can not be found, both will be 0. - public delegate void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry); - - [LibraryImport("unwinder_cdac_arm64", EntryPoint = "arm64Unwind")] - private static partial int ARM64Unwind( - ref ARM64Context context, - [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, - [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, - [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); - [LibraryImport("unwinder_cdac_amd64", EntryPoint = "amd64Unwind")] - private static partial int AMD64Unwind( - ref AMD64Context context, - [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, - [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, - [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); - - public static int AMD64Unwind( - ref AMD64Context context, - Target target) + private void Unwind(ThreadData threadData) + where T : struct, IContext { - ReadCallback readCallback; - GetAllocatedBuffer getAllocatedBuffer; - GetStackWalkInfo getStackWalkInfo; - - // Move to IDisposable for freeing - List allocatedRegions = []; + IContext context = GetThreadContext(threadData); + UnwindUntilNative(ref context); - readCallback = (address, pBuffer, bufferSize) => - { - Span span = new Span(pBuffer, bufferSize); - target.ReadBuffer(address, span); - return 0; - }; - getAllocatedBuffer = (bufferSize, ppBuffer) => + foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) { - *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); - IntPtr pBuffer = new(*ppBuffer); - //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); - allocatedRegions.Add(pBuffer); - return 0; - }; - getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => - { - IExecutionManager eman = target.Contracts.ExecutionManager; - - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; - - try - { - if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) - { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; - } - } - catch (System.Exception ex) + FrameIterator.PrintFrame(_target, frame); + if (FrameIterator.TryUpdateContext(_target, frame, ref context)) { - Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + UnwindUntilNative(ref context); } - }; - - int ret = AMD64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); - - foreach (IntPtr ptr in allocatedRegions) - { - //Console.WriteLine($"Freeing buffer at {ptr:x16}"); - NativeMemory.Free(ptr.ToPointer()); } - - return ret; } - public static int ARM64Unwind( - ref ARM64Context context, - Target target) + private void UnwindUntilNative(ref IContext context) { - ReadCallback readCallback; - GetAllocatedBuffer getAllocatedBuffer; - GetStackWalkInfo getStackWalkInfo; + IExecutionManager eman = _target.Contracts.ExecutionManager; - // Move to IDisposable for freeing - List allocatedRegions = []; + TargetPointer startSp = context.StackPointer; - readCallback = (address, pBuffer, bufferSize) => - { - Span span = new Span(pBuffer, bufferSize); - target.ReadBuffer(address, span); - return 0; - }; - getAllocatedBuffer = (bufferSize, ppBuffer) => - { - *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); - IntPtr pBuffer = new(*ppBuffer); - //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); - allocatedRegions.Add(pBuffer); - return 0; - }; - getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + while (eman.GetCodeBlockHandle(new(context.InstructionPointer)) is CodeBlockHandle cbh && cbh.Address != TargetPointer.Null) { - IExecutionManager eman = target.Contracts.ExecutionManager; - - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; - + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + Console.WriteLine($"[{context.GetType().Name}] IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16} MethodDesc: {methodDesc.Value:x16}"); try { - if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) - { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; - } + context.Unwind(_target); + } catch (System.Exception ex) { - Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + Console.WriteLine(ex); + throw; } - }; - - int ret = ARM64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); - - foreach (IntPtr ptr in allocatedRegions) - { - //Console.WriteLine($"Freeing buffer at {ptr:x16}"); - NativeMemory.Free(ptr.ToPointer()); } - return ret; + Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16}"); + Console.WriteLine($"IP is unmanaged, finishing unwind started at {startSp.Value:x16}"); } -} +}; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs new file mode 100644 index 00000000000000..008688dba96276 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs @@ -0,0 +1,158 @@ +// 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.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; +internal static unsafe partial class Unwinder +{ + // ReadCallback allows the unwinder to read memory from the target process + // into an allocated buffer. This buffer is either allocated by the unwinder + // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. + // In the latter case, the unwinder can only use the buffer for the duration of the + // unwind call. Once the call is over the cDAC will free all allocated buffers. + public delegate int ReadCallback(ulong address, void* buffer, int bufferSize); + public delegate int GetAllocatedBuffer(int bufferSize, void** buffer); + + // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp + // To maintain the same signature as the original function, we return void. + // If the moduleBase or funcEntry can not be found, both will be 0. + public delegate void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry); + + [LibraryImport("unwinder_cdac_arm64", EntryPoint = "arm64Unwind")] + private static partial int ARM64Unwind( + ref ARM64Context context, + [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, + [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, + [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + + [LibraryImport("unwinder_cdac_amd64", EntryPoint = "amd64Unwind")] + private static partial int AMD64Unwind( + ref AMD64Context context, + [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, + [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, + [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + + public static int AMD64Unwind( + ref AMD64Context context, + Target target) + { + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + + // Move to IDisposable for freeing + List allocatedRegions = []; + + readCallback = (address, pBuffer, bufferSize) => + { + Span span = new Span(pBuffer, bufferSize); + target.ReadBuffer(address, span); + return 0; + }; + getAllocatedBuffer = (bufferSize, ppBuffer) => + { + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + IntPtr pBuffer = new(*ppBuffer); + //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); + allocatedRegions.Add(pBuffer); + return 0; + }; + getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + { + IExecutionManager eman = target.Contracts.ExecutionManager; + + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + } + }; + + int ret = AMD64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); + + foreach (IntPtr ptr in allocatedRegions) + { + //Console.WriteLine($"Freeing buffer at {ptr:x16}"); + NativeMemory.Free(ptr.ToPointer()); + } + + return ret; + } + + public static int ARM64Unwind( + ref ARM64Context context, + Target target) + { + ReadCallback readCallback; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + + // Move to IDisposable for freeing + List allocatedRegions = []; + + readCallback = (address, pBuffer, bufferSize) => + { + Span span = new Span(pBuffer, bufferSize); + target.ReadBuffer(address, span); + return 0; + }; + getAllocatedBuffer = (bufferSize, ppBuffer) => + { + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + IntPtr pBuffer = new(*ppBuffer); + //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); + allocatedRegions.Add(pBuffer); + return 0; + }; + getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + { + IExecutionManager eman = target.Contracts.ExecutionManager; + + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + { + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + } + } + catch (System.Exception ex) + { + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + } + }; + + int ret = ARM64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); + + foreach (IntPtr ptr in allocatedRegions) + { + //Console.WriteLine($"Freeing buffer at {ptr:x16}"); + NativeMemory.Free(ptr.ToPointer()); + } + + return ret; + } +} 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 81b5ec7a39fdc1..7213f4601d30dd 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 @@ -15,11 +15,11 @@ internal sealed class Frame : IData DataType.HelperMethodFrame_2OBJ, DataType.HelperMethodFrame_3OBJ, DataType.HelperMethodFrame_PROTECTOBJ, + DataType.SoftwareExceptionFrame, DataType.ResumableFrame, DataType.RedirectedTHreadFrame, DataType.FaultingExceptionFrame, - DataType.SoftwareExceptionFrame, DataType.FuncEvalFrame, DataType.UnmanagedToManagedFrame, DataType.ComMethodFrame, diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs new file mode 100644 index 00000000000000..11728e08cbedac --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.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 SoftwareExceptionFrame : IData +{ + static SoftwareExceptionFrame IData.Create(Target target, TargetPointer address) + => new SoftwareExceptionFrame(target, address); + + public SoftwareExceptionFrame(Target target, TargetPointer address) + { + // TypeInfo will only exist if FEATURE_EH_FUNCLETS is enabled. + // If it doesn't exist, then this type of frame is not present. + Target.TypeInfo type = target.GetTypeInfo(DataType.SoftwareExceptionFrame); + TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; + ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + } + + public TargetPointer TargetContext { get; } + public TargetPointer ReturnAddress { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 6367b4ed23ce34..be495b2c6a4aac 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -239,12 +239,12 @@ public override int GetThreadContext(uint threadId, uint contextFlags, uint cont return hr; } - public override int GetPlatform(out int platform) + public override int GetPlatform(out CorDebugPlatform platform) { if (getTargetPlatform is null) throw new InvalidOperationException("GetTargetPlatform is not available"); - - int hr = getTargetPlatform(out platform); + int hr = getTargetPlatform(out int platformInt); + platform = (CorDebugPlatform)platformInt; return hr; } diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index c4564c0c453047..43565a4efcf88e 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -231,7 +231,7 @@ public override int GetThreadContext(uint threadId, uint contextFlags, uint cont throw new InvalidOperationException("GetThreadContext is not available"); } - public override int GetPlatform(out int platform) => throw new NotImplementedException(); + public override int GetPlatform(out CorDebugPlatform platform) => throw new NotImplementedException(); public override Target.IDataCache ProcessedData => _dataCache; public override ContractRegistry Contracts => _contractRegistry; From 3d22034611e168733c524bad9beefcef40b9b87f Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 30 Jan 2025 20:22:08 -0500 Subject: [PATCH 11/63] AMD64 stackwalking through SOS --- .../Contracts/IStackWalk.cs | 15 +- .../Contracts/StackWalk/AMD64Context.cs | 23 +- .../Contracts/StackWalk/ARM64Context.cs | 26 +- .../Contracts/StackWalk/FrameIterator.cs | 48 +++- .../Contracts/StackWalk/IContext.cs | 14 +- .../Contracts/StackWalk/StackWalk_1.cs | 264 ++++++++++++------ .../Data/Frames/InlinedCallFrame.cs | 2 + .../Data/Frames/SoftwareExceptionFrame.cs | 2 + .../cdacreader/src/Legacy/ClrDataStackWalk.cs | 123 +++++++- .../cdacreader/src/Legacy/SOSDacImpl.cs | 12 - 10 files changed, 403 insertions(+), 126 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index 6297c57802108a..b6d6c6367d44d3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -5,11 +5,24 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; +public interface IStackDataFrameHandle { }; +public interface IStackWalkHandle { }; + internal interface IStackWalk : IContract { static string IContract.Name => nameof(StackWalk); - public void TestEntry() => throw new NotImplementedException(); + public virtual IStackWalkHandle CreateStackWalk(ThreadData threadData) => throw new NotImplementedException(); + + public virtual bool Next(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); + + public virtual IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); + + public virtual byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); + + public virtual TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); + + public virtual void Print(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); } internal struct StackWalk : IStackWalk diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs index 58f9a4320b1aed..3e78d57177e729 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs @@ -29,8 +29,8 @@ public enum ContextFlagsValues : uint CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x00000080, } - public static uint Size => 0x4d0; - public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + public uint Size => 0x4d0; + public uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public TargetPointer StackPointer { @@ -53,11 +53,9 @@ public void Unwind(Target target) Unwinder.AMD64Unwind(ref this, target); } - public unsafe void ReadFromAddress(Target target, TargetPointer address) + public unsafe void FillFromBuffer(Span buffer) { - Span buffer = new byte[Size]; - target.ReadBuffer(address, buffer); - Span structSpan = MemoryMarshal.CreateSpan(ref this, sizeof(AMD64Context)); + Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); Span byteSpan = MemoryMarshal.Cast(structSpan); if (buffer.Length > sizeof(AMD64Context)) { @@ -69,6 +67,19 @@ public unsafe void ReadFromAddress(Target target, TargetPointer address) } } + public unsafe byte[] GetBytes() + { + Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); + Span byteSpan = MemoryMarshal.AsBytes(structSpan); + return byteSpan.ToArray(); + } + + public IContext Clone() + { + AMD64Context clone = this; + return clone; + } + public void Clear() { this = default; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs index 9295ddec48e0a0..4e15c6583fb8e5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs @@ -27,9 +27,9 @@ public enum ContextFlagsValues : uint CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, } - public static uint Size => 0x390; + public uint Size => 0x390; - public static uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + public uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public TargetPointer StackPointer { @@ -56,12 +56,9 @@ public void Clear() { this = default; } - - public unsafe void ReadFromAddress(Target target, TargetPointer address) + public unsafe void FillFromBuffer(Span buffer) { - Span buffer = new byte[Size]; - target.ReadBuffer(address, buffer); - Span structSpan = MemoryMarshal.CreateSpan(ref this, sizeof(ARM64Context)); + Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); Span byteSpan = MemoryMarshal.Cast(structSpan); if (buffer.Length > sizeof(ARM64Context)) { @@ -73,10 +70,23 @@ public unsafe void ReadFromAddress(Target target, TargetPointer address) } } + public unsafe byte[] GetBytes() + { + Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); + Span byteSpan = MemoryMarshal.AsBytes(structSpan); + return byteSpan.ToArray(); + } + + public IContext Clone() + { + ARM64Context clone = this; + return clone; + } + public override string ToString() { StringBuilder sb = new(); - foreach (FieldInfo fieldInfo in typeof(AMD64Context).GetFields()) + foreach (FieldInfo fieldInfo in typeof(ARM64Context).GetFields()) { switch (fieldInfo.GetValue(this)) { 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 index 1b19d5a4513aa1..e8cfa5f6854f19 100644 --- 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 @@ -13,6 +13,40 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal sealed class FrameIterator { + 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(ref IContext context) + { + return TryUpdateContext(target, CurrentFrame, ref context); + } + public static IEnumerable EnumerateFrames(Target target, TargetPointer framePointer) { TargetPointer terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); @@ -66,6 +100,16 @@ public static bool TryUpdateContext(Target target, Data.Frame frame, ref IContex return false; } + public bool IsInlinedWithActiveCall() + { + if (CurrentFrame.Type != DataType.InlinedCallFrame) + { + return false; + } + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(currentFramePointer); + return inlinedCallFrame.CallerReturnAddress != 0; + } + public static void PrintFrame(Target target, Data.Frame frame) { switch (frame.Type) @@ -94,14 +138,14 @@ public static void PrintFrame(Target target, Data.Frame frame) public static void Print(InlinedCallFrame inlinedCallFrame) { - Console.WriteLine($"[InlinedCallFrame: IP={inlinedCallFrame.CallerReturnAddress}, SP={inlinedCallFrame.CallSiteSP}, FP={inlinedCallFrame.CalleeSavedFP}]"); + Console.WriteLine($"[{nameof(InlinedCallFrame),-30}: Address={inlinedCallFrame.Address} IP={inlinedCallFrame.CallerReturnAddress}, SP={inlinedCallFrame.CallSiteSP}, FP={inlinedCallFrame.CalleeSavedFP}]"); } public static void Print(Target target, SoftwareExceptionFrame softwareExceptionFrame) { IContext context = IContext.GetContextForPlatform(target); context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); - Console.WriteLine($"[SoftwareExceptionFrame: IP={context.InstructionPointer.Value:x16}, SP={context.StackPointer.Value:x16}, FP={context.FramePointer.Value:x16}]"); + Console.WriteLine($"[{nameof(SoftwareExceptionFrame),-30}: Address={softwareExceptionFrame.Address} IP={context.InstructionPointer.Value:x16}, SP={context.StackPointer.Value:x16}, FP={context.FramePointer.Value:x16}]"); } public static void Print(HelperMethodFrame helperMethodFrame) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs index d88ad262336fe2..7bcfa77a2c9dbf 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs @@ -8,15 +8,23 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; internal interface IContext { - public static abstract uint Size { get; } - public static abstract uint DefaultContextFlags { get; } + public abstract uint Size { get; } + public abstract uint DefaultContextFlags { get; } public TargetPointer StackPointer { get; set; } public TargetPointer InstructionPointer { get; set; } public TargetPointer FramePointer { get; set; } public abstract void Clear(); - public abstract void ReadFromAddress(Target target, TargetPointer address); + public unsafe void ReadFromAddress(Target target, TargetPointer address) + { + Span buffer = new byte[Size]; + target.ReadBuffer(address, buffer); + FillFromBuffer(buffer); + } + public abstract void FillFromBuffer(Span buffer); + public abstract byte[] GetBytes(); + public abstract IContext Clone(); public abstract void Unwind(Target target); public static IContext GetContextForPlatform(Target target) 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 5da16ce49acdb2..75cad57ccf50b5 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 @@ -3,12 +3,9 @@ using System; using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -using Microsoft.Diagnostics.DataContractReader; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -21,138 +18,223 @@ internal StackWalk_1(Target target) _target = target; } - private T GetThreadContext(ThreadData threadData) - where T : struct, IContext + public enum StackWalkState { - int hr; - unsafe - { - byte[] bytes = new byte[T.Size]; - Span buffer = new Span(bytes); - hr = _target.GetThreadContext((uint)threadData.OSId.Value, T.DefaultContextFlags, (uint)T.Size, buffer); - if (hr != 0) - { - throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); - } + SW_COMPLETE, + SW_ERROR, + + // The current Context is managed + SW_FRAMELESS, + + // The current Context is unmanaged. + // The next update will use a Frame to get a managed context + // When SW_FRAME, the FrameAddress is valid + SW_FRAME, + SW_SKIPPED_FRAME, + } + + internal struct StackDataFrameHandle : IStackDataFrameHandle + { + internal IContext Context { get; init; } + internal StackWalkState State { get; init; } + internal TargetPointer FrameAddress { get; init; } + } + + internal class StackWalkHandle : IStackWalkHandle + { + public StackWalkState state; + public IContext context; + public FrameIterator frameIter; - T context = default; - Span structSpan = MemoryMarshal.CreateSpan(ref context, sizeof(T)); - Span byteSpan = MemoryMarshal.Cast(structSpan); - buffer.Slice(0, sizeof(T)).CopyTo(byteSpan); - return context; + public StackWalkHandle(IContext context, FrameIterator frameIter, StackWalkState state) + { + this.context = context; + this.frameIter = frameIter; + this.state = state; } } - void IStackWalk.TestEntry() + IStackWalkHandle IStackWalk.CreateStackWalk(ThreadData threadData) { - string outputhPath = "C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt"; - using StreamWriter writer = new StreamWriter(outputhPath); - Console.SetOut(writer); + IContext context = IContext.GetContextForPlatform(_target); + FillContextFromThread(ref context, threadData); + StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; + return new StackWalkHandle(context, new(_target, threadData), state); + } - try + private void UpdateState(StackWalkHandle handle) + { + // If we are complete or in a bad state, no updating is required. + if (handle.state is StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE) { - Handle(); + return; } - catch (System.Exception ex) + + bool isManaged = IsManaged(handle.context.InstructionPointer, out _); + bool validFrame = handle.frameIter.IsValid(); + + if (isManaged) { - Console.WriteLine(ex); + handle.state = StackWalkState.SW_FRAMELESS; + if (CheckForSkippedFrames(handle)) + { + handle.state = StackWalkState.SW_SKIPPED_FRAME; + return; + } } - finally + else { - writer.Close(); + handle.state = validFrame ? StackWalkState.SW_FRAME : StackWalkState.SW_COMPLETE; } } - private void Handle() + bool IStackWalk.Next(IStackWalkHandle stackWalkHandle) { - ThreadStoreData tsdata = _target.Contracts.Thread.GetThreadStoreData(); - ThreadData threadData = _target.Contracts.Thread.GetThreadData(tsdata.FirstThread); + StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); - _target.GetPlatform(out Target.CorDebugPlatform platform); - Console.WriteLine(platform.ToString()); - try + switch (handle.state) { - IContext context; - switch (platform) - { - case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: - case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: - case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: - context = GetThreadContext(threadData); - Console.WriteLine($"[{context.GetType().Name}: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - Unwind(threadData); - break; - case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: - case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: - context = GetThreadContext(threadData); - Console.WriteLine($"[{context.GetType().Name}: RIP={context.InstructionPointer.Value:x16} RSP={context.StackPointer.Value:x16}]"); - Unwind(threadData); - break; - default: - throw new ArgumentOutOfRangeException(nameof(platform), platform, null); - } + case StackWalkState.SW_FRAMELESS: + try + { + handle.context.Unwind(_target); + } + catch + { + handle.state = StackWalkState.SW_ERROR; + throw; + } + break; + case StackWalkState.SW_SKIPPED_FRAME: + handle.frameIter.Next(); + break; + case StackWalkState.SW_FRAME: + handle.frameIter.TryUpdateContext(ref handle.context); + if (!handle.frameIter.IsInlinedWithActiveCall()) + { + handle.frameIter.Next(); + } + break; + case StackWalkState.SW_ERROR: + case StackWalkState.SW_COMPLETE: + return false; } - catch (System.Exception ex) + UpdateState(handle); + + return handle.state is not (StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE); + } + + IStackDataFrameHandle IStackWalk.GetCurrentFrame(IStackWalkHandle stackWalkHandle) + { + StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); + return new StackDataFrameHandle + { + Context = handle.context.Clone(), + State = handle.state, + FrameAddress = handle.frameIter.CurrentFrameAddress, + }; + } + + private bool CheckForSkippedFrames(StackWalkHandle handle) + { + // ensure we can find the caller context + Debug.Assert(IsManaged(handle.context.InstructionPointer, out _)); + + // if there are no more Frames, vacuously false + if (!handle.frameIter.IsValid()) { - Console.WriteLine(ex); + return false; } + + // get the caller context + IContext parentContext = handle.context.Clone(); + parentContext.Unwind(_target); + + return handle.frameIter.CurrentFrameAddress.Value < parentContext.StackPointer.Value; } - private void CheckIP(TargetCodePointer ip) + byte[] IStackWalk.GetRawContext(IStackDataFrameHandle stackDataFrameHandle) { + StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); + return handle.Context.GetBytes(); + } + + TargetPointer IStackWalk.GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) + { + StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); + if (handle.State is StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME) + { + return handle.FrameAddress; + } + return TargetPointer.Null; + } + + void IStackWalk.Print(IStackWalkHandle stackWalkHandle) + { + StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); IExecutionManager eman = _target.Contracts.ExecutionManager; + + TargetCodePointer ip = CodePointerUtils.CodePointerFromAddress(handle.context.InstructionPointer, _target); if (eman.GetCodeBlockHandle(ip) is CodeBlockHandle cbh) { TargetPointer methodDesc = eman.GetMethodDesc(cbh); TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, ip); - Console.WriteLine($"MethodDesc: {methodDesc.Value:x16} BaseAddress: {moduleBase.Value:x16} UnwindInfo: {unwindInfo.Value:x16}"); + Console.WriteLine($"[{handle.context.GetType().Name}] State={handle.state,-20} SP={handle.context.StackPointer.Value:x16} IP={handle.context.InstructionPointer.Value:x16} MethodDesc={methodDesc.Value:x16} BaseAddress={moduleBase.Value:x16}"); } else { - Console.WriteLine("IP is unmanaged"); + Console.WriteLine($"[{handle.context.GetType().Name}] State={handle.state,-20} SP={handle.context.StackPointer.Value:x16} IP={handle.context.InstructionPointer.Value:x16} Unmanaged"); + } + + if (handle.frameIter.IsValid()) + { + FrameIterator.PrintFrame(_target, handle.frameIter.CurrentFrame); } } - private void Unwind(ThreadData threadData) - where T : struct, IContext + private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle? codeBlockHandle) { - IContext context = GetThreadContext(threadData); - UnwindUntilNative(ref context); - - foreach (Data.Frame frame in FrameIterator.EnumerateFrames(_target, threadData.Frame)) + IExecutionManager eman = _target.Contracts.ExecutionManager; + TargetCodePointer codePointer = CodePointerUtils.CodePointerFromAddress(ip, _target); + if (eman.GetCodeBlockHandle(codePointer) is CodeBlockHandle cbh && cbh.Address != TargetPointer.Null) { - FrameIterator.PrintFrame(_target, frame); - if (FrameIterator.TryUpdateContext(_target, frame, ref context)) - { - UnwindUntilNative(ref context); - } + codeBlockHandle = cbh; + return true; } + codeBlockHandle = default; + return false; } - private void UnwindUntilNative(ref IContext context) + private static StackWalkHandle AssertCorrectHandle(IStackWalkHandle stackWalkHandle) { - IExecutionManager eman = _target.Contracts.ExecutionManager; + if (stackWalkHandle is not StackWalkHandle handle) + { + throw new ArgumentException("Invalid stack walk handle", nameof(stackWalkHandle)); + } - TargetPointer startSp = context.StackPointer; + return handle; + } - while (eman.GetCodeBlockHandle(new(context.InstructionPointer)) is CodeBlockHandle cbh && cbh.Address != TargetPointer.Null) + private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle stackDataFrameHandle) + { + if (stackDataFrameHandle is not StackDataFrameHandle handle) { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - Console.WriteLine($"[{context.GetType().Name}] IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16} MethodDesc: {methodDesc.Value:x16}"); - try - { - context.Unwind(_target); + throw new ArgumentException("Invalid stack data frame handle", nameof(stackDataFrameHandle)); + } - } - catch (System.Exception ex) - { - Console.WriteLine(ex); - throw; - } + return handle; + } + + private unsafe void FillContextFromThread(ref IContext refContext, ThreadData threadData) + { + byte[] bytes = new byte[refContext.Size]; + Span buffer = new Span(bytes); + int hr = _target.GetThreadContext((uint)threadData.OSId.Value, refContext.DefaultContextFlags, refContext.Size, buffer); + if (hr != 0) + { + throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); } - Console.WriteLine($"IP: {context.InstructionPointer.Value:x16} SP: {context.StackPointer.Value:x16}"); - Console.WriteLine($"IP is unmanaged, finishing unwind started at {startSp.Value:x16}"); + refContext.FillFromBuffer(buffer); } }; 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 0b2cce103cf6a1..78af563e607985 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 @@ -14,8 +14,10 @@ public InlinedCallFrame(Target target, TargetPointer address) CallSiteSP = target.ReadPointer(address + (ulong)type.Fields[nameof(CallSiteSP)].Offset); CallerReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(CallerReturnAddress)].Offset); CalleeSavedFP = target.ReadPointer(address + (ulong)type.Fields[nameof(CalleeSavedFP)].Offset); + Address = address; } + 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/SoftwareExceptionFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs index 11728e08cbedac..9de354ae54dcb3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs @@ -13,10 +13,12 @@ public SoftwareExceptionFrame(Target target, TargetPointer address) // TypeInfo will only exist if FEATURE_EH_FUNCLETS is enabled. // If it doesn't exist, then this type of frame is not present. Target.TypeInfo type = target.GetTypeInfo(DataType.SoftwareExceptionFrame); + Address = address; TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); } + public TargetPointer Address { get;} public TargetPointer TargetContext { get; } public TargetPointer ReturnAddress { get; } } diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index 0e543c87fccdff..313bf09995d47e 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -2,8 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -15,16 +19,62 @@ internal sealed unsafe partial class ClrDataStackWalk : IXCLRDataStackWalk private readonly Target _target; private readonly IXCLRDataStackWalk? _legacyImpl; + private readonly IStackWalkHandle _stackWalkHandle; + public ClrDataStackWalk(TargetPointer threadAddr, uint flags, Target target, IXCLRDataStackWalk? legacyImpl) { _threadAddr = threadAddr; _flags = flags; _target = target; _legacyImpl = legacyImpl; + + ThreadData threadData = _target.Contracts.Thread.GetThreadData(_threadAddr); + _stackWalkHandle = _target.Contracts.StackWalk.CreateStackWalk(threadData); } int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextSize, [MarshalUsing(CountElementName = "contextBufSize"), Out] byte[] contextBuf) - => _legacyImpl is not null ? _legacyImpl.GetContext(contextFlags, contextBufSize, contextSize, contextBuf) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + IStackWalk sw = _target.Contracts.StackWalk; + IStackDataFrameHandle dataFrame = sw.GetCurrentFrame(_stackWalkHandle); + byte[] context = sw.GetRawContext(dataFrame); + if (context.Length > contextBufSize) + hr = HResults.E_INVALIDARG; + + if (contextSize is not null) + { + *contextSize = (uint)context.Length; + } + + context.CopyTo(contextBuf); + +#if DEBUG + if (_legacyImpl is not null) + { + byte[] localContextBuf = new byte[contextBufSize]; + int hrLocal = _legacyImpl.GetContext(contextFlags, contextBufSize, null, localContextBuf); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + IContext contextStruct = IContext.GetContextForPlatform(_target); + IContext localContextStruct = IContext.GetContextForPlatform(_target); + 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}"); + } +#endif + + return HResults.S_OK; + + // if (_legacyImpl is not null) + // { + // _legacyImpl.GetContext(contextFlags, contextBufSize, contextSize, contextBuf); + // } + } + int IXCLRDataStackWalk.GetFrame(void** frame) => _legacyImpl is not null ? _legacyImpl.GetFrame(frame) : HResults.E_NOTIMPL; int IXCLRDataStackWalk.GetFrameType(uint* simpleType, uint* detailedType) @@ -32,9 +82,76 @@ int IXCLRDataStackWalk.GetFrameType(uint* simpleType, uint* detailedType) int IXCLRDataStackWalk.GetStackSizeSkipped(ulong* stackSizeSkipped) => _legacyImpl is not null ? _legacyImpl.GetStackSizeSkipped(stackSizeSkipped) : HResults.E_NOTIMPL; int IXCLRDataStackWalk.Next() - => _legacyImpl is not null ? _legacyImpl.Next() : HResults.E_NOTIMPL; + { + int hr; + try + { + hr = _target.Contracts.StackWalk.Next(_stackWalkHandle) ? HResults.S_OK : HResults.S_FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal = _legacyImpl.Next(); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } +#endif + + return hr; + } int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) - => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; + { + string outputhPath = "C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt"; + using StreamWriter writer = new StreamWriter(outputhPath, true); + Console.SetOut(writer); + + const uint DACSTACKPRIV_REQUEST_FRAME_DATA = 0xf0000000; + + int hr = HResults.S_OK; + + switch (reqCode) + { + case DACSTACKPRIV_REQUEST_FRAME_DATA: + if (outBufferSize < sizeof(ulong)) + hr = HResults.E_INVALIDARG; + + IStackWalk sw = _target.Contracts.StackWalk; + IStackDataFrameHandle frameData = sw.GetCurrentFrame(_stackWalkHandle); + TargetPointer frameAddr = sw.GetFrameAddress(frameData); + *(ulong*)outBuffer = frameAddr.Value; + hr = HResults.S_OK; + break; + default: + hr = HResults.E_NOTIMPL; + break; + } + +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal; + byte[] localOutBuffer = new byte[outBufferSize]; + fixed (byte* localOutBufferPtr = localOutBuffer) + { + hrLocal = _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, localOutBufferPtr); + } + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + for (int i = 0; i < outBufferSize; i++) + { + // Console.WriteLine($"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); + Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); + } + } +#endif + writer.Close(); + + return hr; + } int IXCLRDataStackWalk.SetContext(uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context) => _legacyImpl is not null ? _legacyImpl.SetContext(contextSize, context) : HResults.E_NOTIMPL; int IXCLRDataStackWalk.SetContext2(uint flags, uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 3dccfab0865bb2..a16817d57151e0 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -570,18 +570,6 @@ int ISOSDacInterface.GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromFrame(frameAddr, ppMD) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) { - try - { - if (ip == 1) - { - _target.Contracts.StackWalk.TestEntry(); - } - } - catch - { - Console.WriteLine("uh oh"); - } - return _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromIP(ip, ppMD) : HResults.E_NOTIMPL; } int ISOSDacInterface.GetMethodDescTransparencyData(ulong methodDesc, void* data) From 18e63a30b9c1e8afefd76d0b1e28ff1b769df7cb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Feb 2025 16:53:28 -0500 Subject: [PATCH 12/63] clean up PR --- docs/design/datacontracts/ExecutionManager.md | 6 + docs/design/datacontracts/StackWalk.md | 67 ++++++ eng/native/functions.cmake | 1 + src/coreclr/debug/daccess/cdac.cpp | 10 +- .../debug/runtimeinfo/datadescriptor.h | 24 -- src/coreclr/unwinder/CMakeLists.txt | 22 +- src/coreclr/unwinder/amd64/unwinder.cpp | 71 +++--- src/coreclr/unwinder/amd64/unwinder.h | 12 +- src/coreclr/unwinder/arm64/unwinder.cpp | 24 +- src/coreclr/unwinder/arm64/unwinder.h | 9 +- src/coreclr/unwinder/baseunwinder.cpp | 14 +- src/coreclr/unwinder/baseunwinder.h | 26 ++- src/coreclr/vm/amd64/gmscpu.h | 10 - src/coreclr/vm/frames.h | 50 ----- .../Contracts/IStackWalk.cs | 8 +- .../Target.cs | 19 +- .../ExecutionManagerBase.EEJitManager.cs | 28 +-- .../Helpers/RuntimeFunctionLookup.cs | 18 +- .../Contracts/StackWalk/FrameIterator.cs | 101 +-------- .../Contracts/StackWalk/IContext.cs | 5 +- .../Contracts/StackWalk/StackWalk_1.cs | 25 +-- .../Contracts/StackWalk/Unwinder.cs | 206 ++++++++---------- .../Contracts/Thread_1.cs | 1 - .../Data/Frames/Frame.cs | 19 +- .../Data/Frames/HelperMethodFrame.cs | 53 ----- .../Data/Frames/LazyMachState.cs | 34 --- .../Data/Frames/SoftwareExceptionFrame.cs | 4 +- .../Data/RealCodeHeader.cs | 2 + .../ContractDescriptorTarget.cs | 50 +++-- .../managed/cdacreader/inc/cdac_reader.h | 3 +- .../cdacreader/src/Legacy/ClrDataStackWalk.cs | 13 -- .../cdacreader/src/Legacy/SOSDacImpl.cs | 4 +- .../managed/cdacreader/src/cdacreader.csproj | 2 +- .../ContractDescriptorBuilder.cs | 9 +- .../cdacreader/tests/TestPlaceholderTarget.cs | 9 +- 35 files changed, 365 insertions(+), 594 deletions(-) create mode 100644 docs/design/datacontracts/StackWalk.md delete mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs delete mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 8504456e1d749e..99e66401a0c326 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -23,6 +23,10 @@ struct CodeBlockHandle TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle); // Get the instruction pointer address of the start of the code block TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle); + // Gets the base address of the module containing the code block + TargetPointer GetModuleBaseAddress(CodeBlockHandle codeInfoHandle); + // Gets the unwind info of the code block at the specified code pointer + TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip); ``` ## Version 1 @@ -53,6 +57,8 @@ Data descriptors used: | `CodeHeapListNode` | `MapBase` | Start of the map - start address rounded down based on OS page size | | `CodeHeapListNode` | `HeaderMap` | Bit array used to find the start of methods - relative to `MapBase` | | `RealCodeHeader` | `MethodDesc` | Pointer to the corresponding `MethodDesc` | +| `RealCodeHeader` | `NumUnwindInfos` | Number of Unwind Infos | +| `RealCodeHeader` | `UnwindInfos` | Start address of Unwind Infos | | `Module` | `ReadyToRunInfo` | Pointer to the `ReadyToRunInfo` for the module | | `ReadyToRunInfo` | `CompositeInfo` | Pointer to composite R2R info - or itself for non-composite | | `ReadyToRunInfo` | `NumRuntimeFunctions` | Number of `RuntimeFunctions` | diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md new file mode 100644 index 00000000000000..66da186f5be5a4 --- /dev/null +++ b/docs/design/datacontracts/StackWalk.md @@ -0,0 +1,67 @@ +# Contract StackWalk + +This contract encapsulates support for StackWalking managed threads. + +## APIs of contract + +```csharp +public interface IStackWalkHandle { }; +public interface IStackDataFrameHandle { }; +``` + +```csharp +// Creates a stack walk and returns a handle +IStackWalkHandle CreateStackWalk(ThreadData threadData); +// Iterates the stackWalkHandle to the next frame. If successful, returns true. Otherwise false. +bool Next(IStackWalkHandle stackWalkHandle); +// Gets the current frame from a stack walk and returns a IStackDataFrameHandle to it. +IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); +// Gets the thread context at the given stack dataframe. +byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); +// Gets the Frame address at the given stack dataframe. Returns TargetPointer.Null if the current dataframe does not have a valid Frame. +TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); +``` + +## Version 1 + +The `StackWalk` contract provides an interface for walking the stack of a managed thread. It includes methods to create a stack walk, move to the next frame, get the current frame, retrieve the raw context, and get the frame address. + +### CreateStackWalk + +```csharp +IStackWalkHandle CreateStackWalk(ThreadData threadData); +``` + +Creates a stack walk handle for the given thread data. This initializes the context and frame iterator for the stack walk. + +### Next + +```csharp +bool Next(IStackWalkHandle stackWalkHandle); +``` + +Moves to the next frame in the stack walk. Returns `true` if successfully moved to the next frame. Otherwise returns `false`. + +### GetCurrentFrame + +```csharp +IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); +``` + +Gets the current frame in the stack walk. Returns a handle to the stack data frame. + +### GetRawContext + +```csharp +byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); +``` + +Retrieves the raw Windows thread context of the current frame as a byte array. The size and shape of the context is platform dependent. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. + +### GetFrameAddress + +```csharp +TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); +``` + +Gets the frame address of the current frame. Returns `TargetPointer.Null` if the frame is not valid. diff --git a/eng/native/functions.cmake b/eng/native/functions.cmake index a1c11ae27430f4..9b8542b35ee5b4 100644 --- a/eng/native/functions.cmake +++ b/eng/native/functions.cmake @@ -555,6 +555,7 @@ function(install_clr) endif() foreach(destination ${destinations}) + # Install the export libraries for static libraries. if (${INSTALL_CLR_INSTALL_ALL_ARTIFACTS}) install(TARGETS ${targetName} DESTINATION ${destination} COMPONENT ${INSTALL_CLR_COMPONENT}) else() diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index cd2046cbf22bf4..dc04fdf99ccb73 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -25,6 +25,10 @@ namespace iter++; path.Truncate(iter); path.Append(CDAC_LIB_NAME); + + // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR loads dependencies from the same + // directory as cdacreader.dll. Once the native portions of the cDAC + // are statically linked, this won't be required. *phCDAC = CLRLoadLibraryEx(path.GetUnicode(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); if (*phCDAC == NULL) return false; @@ -55,7 +59,11 @@ namespace int GetPlatform(uint32_t* platform, void* context) { ICorDebugDataTarget* target = reinterpret_cast(context); - return target->GetPlatform((CorDebugPlatform*)platform); + HRESULT hr = target->GetPlatform((CorDebugPlatform*)platform); + if (FAILED(hr)) + return hr; + + return S_OK; } } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 14ac85da7f0873..71ca1b7bd3f21e 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -595,30 +595,6 @@ CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*pointer*/, ReturnAddress, cdac_data::FrameAttributes) \ - CDAC_TYPE_FIELD(frameType, /*pointer*/, FCallEntry, cdac_data::FCallEntry) \ - CDAC_TYPE_FIELD(frameType, /*LazyMachState*/, LazyMachState, cdac_data::LazyMachState) \ - CDAC_TYPE_END(frameType) - -DEFINE_FRAME_TYPE(HelperMethodFrame) -DEFINE_FRAME_TYPE(HelperMethodFrame_1OBJ) -DEFINE_FRAME_TYPE(HelperMethodFrame_2OBJ) -DEFINE_FRAME_TYPE(HelperMethodFrame_3OBJ) -DEFINE_FRAME_TYPE(HelperMethodFrame_PROTECTOBJ) -#undef DEFINE_FRAME_TYPE - -#ifdef TARGET_AMD64 -CDAC_TYPE_BEGIN(LazyMachState) -CDAC_TYPE_SIZE(sizeof(LazyMachState)) -CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, InstructionPointer, cdac_data::InstructionPointer) -CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, StackPointer, cdac_data::StackPointer) -CDAC_TYPE_FIELD(LazyMachState, /*pointer*/, ReturnAddress, cdac_data::ReturnAddress) -CDAC_TYPE_END(LazyMachState) -#endif // TARGET_AMD64 - CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index 59e78f36b5163f..8dc7d36afd4a2a 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -1,9 +1,5 @@ set(UNWINDER_SOURCES baseunwinder.cpp -) - -# Include platform specific unwinder for applicable (native and cross-target) builds. -list(APPEND UNWINDER_SOURCES ${ARCH_SOURCES_DIR}/unwinder.cpp ) @@ -37,18 +33,7 @@ target_include_directories(unwinder_dac PRIVATE ${ARCH_SOURCES_DIR}) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) -# add_library_clr(unwinder_cdac SHARED ${UNWINDER_SOURCES}) -# add_dependencies(unwinder_cdac eventing_headers) -# target_link_libraries(unwinder_cdac PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) -# target_compile_definitions(unwinder_cdac PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) -# install_clr(TARGETS unwinder_cdac DESTINATIONS cdaclibs COMPONENT debug INSTALL_ALL_ARTIFACTS) - -# add_library_clr(unwinder_cdac_static ${UNWINDER_SOURCES}) -# add_dependencies(unwinder_cdac_static eventing_headers) -# target_link_libraries(unwinder_cdac_static PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) -# target_compile_definitions(unwinder_cdac_static PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) -# install_clr(TARGETS unwinder_cdac_static DESTINATIONS cdaclibs COMPONENT debug) - +# Helper function for platform specific cDAC uwninder builds. function(create_platform_unwinder) set(oneValueArgs TARGET ARCH) set(multiValueArgs DESTINATIONS) @@ -71,8 +56,6 @@ function(create_platform_unwinder) ${ARCH_SOURCES_DIR}/unwinder.cpp ) - message("${UNWINDER_SOURCES}") - convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) add_library_clr(${TARGETDETAILS_TARGET} @@ -97,11 +80,10 @@ function(create_platform_unwinder) # add the install targets install_clr(TARGETS ${TARGETDETAILS_TARGET} DESTINATIONS ${TARGETDETAILS_DESTINATIONS} COMPONENT debug INSTALL_ALL_ARTIFACTS) - + # Set the target to be built for the specified OS and ARCH set_target_definitions_to_custom_os_and_arch(TARGET ${TARGETDETAILS_TARGET} OS win ARCH ${TARGETDETAILS_ARCH}) target_compile_definitions(${TARGETDETAILS_TARGET} PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) - # target_compile_definitions(${TARGETDETAILS_TARGET} PRIVATE SELF_NO_HOST) endfunction() create_platform_unwinder(TARGET unwinder_cdac_amd64 ARCH x64 DESTINATIONS cdaclibs) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 85c2e7f30c906e..38e40b133e218d 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -52,7 +52,7 @@ M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } -#endif // !FEATURE_CDAC_UNWINDER +#endif // FEATURE_CDAC_UNWINDER #ifdef DACCESS_COMPILE @@ -262,8 +262,29 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) #elif defined(FEATURE_CDAC_UNWINDER) +// TODO: add asserts on cDAC build without importing more headers. #define UNWINDER_ASSERT(x) +// Read 64 bit unsigned value from the specified addres when the unwinder is build +// for the cDAC. This triggers a callback to the cDAC host to read the memory from +// the target process. +ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) +{ + ULONG64 value; + readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); + return value; +} + +// Read 128 bit value from the specified addres when the unwinder is build +// for the cDAC. This triggers a callback to the cDAC host to read the memory from +// the target process. +M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) +{ + M128A value; + readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); + return value; +} + //--------------------------------------------------------------------------------------- // // The InstructionBuffer class abstracts accessing assembler instructions in the function @@ -277,15 +298,16 @@ class InstructionBuffer SIZE_T m_address; UCHAR m_buffer[32]; - ReadCallback readCallback; + ReadFromTarget readFromTarget; + void* callbackContext; // Load the instructions from the target process being debugged HRESULT Load() { - HRESULT hr = readCallback(m_address, m_buffer, sizeof(m_buffer)); + HRESULT hr = readFromTarget(m_address, m_buffer, sizeof(m_buffer), callbackContext); if (SUCCEEDED(hr)) { - // TODO: Implement for cDAC + // TODO: Implement breakpoint patching for cDAC // On X64, we need to replace any patches which are within the requested memory range. // This is because the X64 unwinder needs to disassemble the native instructions in order to determine @@ -298,10 +320,11 @@ class InstructionBuffer public: // Construct the InstructionBuffer for the given address in the target process - InstructionBuffer(SIZE_T address, ReadCallback readCallback) + InstructionBuffer(SIZE_T address, ReadFromTarget readFromTarget, void* callbackContext) : m_offset(0), m_address(address), - readCallback(readCallback) + readFromTarget(readFromTarget), + callbackContext(callbackContext) { HRESULT hr = Load(); if (FAILED(hr)) @@ -348,11 +371,11 @@ class InstructionBuffer } }; -BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) +BOOL amd64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) { HRESULT hr = E_FAIL; - OOPStackUnwinderAMD64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; + OOPStackUnwinderAMD64 unwinder { readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext }; hr = unwinder.Unwind((CONTEXT*) pContext); return (hr == S_OK); @@ -390,7 +413,7 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) { UNWIND_INFO unwindInfo; - if(readCallback((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo)) != S_OK) + if(readFromTarget((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo), callbackContext) != S_OK) { return NULL; } @@ -409,13 +432,15 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) cbUnwindInfo += sizeof(T_RUNTIME_FUNCTION); } + // Allocate a buffer for the unwind info from cDAC callback. + // This buffer will be freed by the cDAC host once unwinding is done. UNWIND_INFO* pUnwindInfo; - if(getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo) != S_OK) + if(getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo, callbackContext) != S_OK) { return NULL; } - if(readCallback(taUnwindInfo, pUnwindInfo, cbUnwindInfo) != S_OK) + if(readFromTarget(taUnwindInfo, pUnwindInfo, cbUnwindInfo, callbackContext) != S_OK) { return NULL; } @@ -423,21 +448,7 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) return pUnwindInfo; } -ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) -{ - ULONG64 value; - readCallback((uint64_t)addr, &value, sizeof(value)); - return value; -} - -M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) -{ - M128A value; - readCallback((uint64_t)addr, &value, sizeof(value)); - return value; -} - -#else // DACCESS_COMPILE +#else // !DACCESS_COMPILE && !FEATURE_CDAC_UNWINDER // Report failure in the unwinder if the condition is FALSE #define UNWINDER_ASSERT _ASSERTE @@ -455,7 +466,6 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) return (UNWIND_INFO *)taUnwindInfo; } -#ifndef FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- // // This function behaves like the RtlVirtualUnwind in Windows. @@ -530,9 +540,8 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( return handlerRoutine; } -#endif // FEATURE_CDAC_UNWINDER -#endif // DACCESS_COMPILE +#endif // !DACCESS_COMPILE && !FEATURE_CDAC_UNWINDER // // @@ -1390,8 +1399,8 @@ Routine Description: if (UnwindVersion < 2) { #ifndef FEATURE_CDAC_UNWINDER InstructionBuffer InstrBuffer = (InstructionBuffer)ControlPc; -#else // !FEATURE_CDAC_UNWINDER - InstructionBuffer InstrBuffer(ControlPc, readCallback); +#else // FEATURE_CDAC_UNWINDER + InstructionBuffer InstrBuffer(ControlPc, readFromTarget, callbackContext); #endif // FEATURE_CDAC_UNWINDER InstructionBuffer NextByte = InstrBuffer; diff --git a/src/coreclr/unwinder/amd64/unwinder.h b/src/coreclr/unwinder/amd64/unwinder.h index cf204abc3e5513..bf1d6e28e44fe6 100644 --- a/src/coreclr/unwinder/amd64/unwinder.h +++ b/src/coreclr/unwinder/amd64/unwinder.h @@ -9,7 +9,11 @@ #include "baseunwinder.h" #ifdef FEATURE_CDAC_UNWINDER -EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo); +EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, + ReadFromTarget readFromTarget, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo, + void* callbackContext); #endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- @@ -21,8 +25,8 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder { #ifdef FEATURE_CDAC_UNWINDER public: - OOPStackUnwinderAMD64(ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) - : OOPStackUnwinder(readCallback, getAllocatedBuffer, getStackWalkInfo) + OOPStackUnwinderAMD64(ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) + : OOPStackUnwinder(readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext) { } #endif // FEATURE_CDAC_UNWINDER @@ -74,8 +78,8 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); -public: ULONG64 MemoryRead64(PULONG64 addr); + M128A MemoryRead128(PM128A addr); }; diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index 2260bfeb9458d5..9b81d20cae5d0f 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -4,11 +4,9 @@ // #include "stdafx.h" - #ifndef FEATURE_CDAC_UNWINDER #include "utilcode.h" #endif // FEATURE_CDAC_UNWINDER - #include "crosscomp.h" #include "unwinder.h" @@ -177,7 +175,7 @@ typedef struct _ARM64_VFP_STATE #if !defined(DEBUGGER_UNWIND) && !defined(FEATURE_CDAC_UNWINDER) #define MEMORY_READ_BYTE(params, addr) (*dac_cast(addr)) -#define MEMORY_READ_WORD(params, addr) (*dac_cast(addr)) +#define MEMORY_READ_WORD(params, addr) (*dac_cast(addr)) #define MEMORY_READ_DWORD(params, addr) (*dac_cast(addr)) #define MEMORY_READ_QWORD(params, addr) (*dac_cast(addr)) @@ -186,13 +184,13 @@ template T cdacRead(uint64_t addr) { T t; - g_pUnwinder->readCallback(addr, &t, sizeof(t)); + g_pUnwinder->readFromTarget(addr, &t, sizeof(t), g_pUnwinder->callbackContext); return t; } -#define MEMORY_READ_BYTE(params, addr) (cdacRead(addr)) -#define MEMORY_READ_WORD(params, addr) (cdacRead(addr)) -#define MEMORY_READ_DWORD(params, addr) (cdacRead(addr)) -#define MEMORY_READ_QWORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_BYTE(params, addr) (cdacRead(addr)) +#define MEMORY_READ_WORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_DWORD(params, addr) (cdacRead(addr)) +#define MEMORY_READ_QWORD(params, addr) (cdacRead(addr)) #endif // @@ -2797,15 +2795,13 @@ BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* p return res; } -#endif // DACCESS_COMPILE - -#ifdef FEATURE_CDAC_UNWINDER +#elif defined(FEATURE_CDAC_UNWINDER) OOPStackUnwinderArm64* g_pUnwinder; -BOOL arm64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) +BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) { HRESULT hr = E_FAIL; - OOPStackUnwinderArm64 unwinder { readCallback, getAllocatedBuffer, getStackWalkInfo }; + OOPStackUnwinderArm64 unwinder { readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext }; g_pUnwinder = &unwinder; hr = unwinder.Unwind((T_CONTEXT*) pContext); @@ -2813,7 +2809,7 @@ BOOL arm64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer g return (hr == S_OK); } -#endif +#endif // FEATURE_CDAC_UNWINDER #if defined(HOST_UNIX) diff --git a/src/coreclr/unwinder/arm64/unwinder.h b/src/coreclr/unwinder/arm64/unwinder.h index 3532e624b4b0f6..0371c9a16ff1b1 100644 --- a/src/coreclr/unwinder/arm64/unwinder.h +++ b/src/coreclr/unwinder/arm64/unwinder.h @@ -9,7 +9,10 @@ #include "baseunwinder.h" #ifdef FEATURE_CDAC_UNWINDER -EXTERN_C __declspec(dllexport) BOOL arm64Unwind(void* pContext, ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo); +EXTERN_C __declspec(dllexport) BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo, + void* callbackContext); #endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- @@ -21,8 +24,8 @@ class OOPStackUnwinderArm64 : public OOPStackUnwinder { #ifdef FEATURE_CDAC_UNWINDER public: - OOPStackUnwinderArm64(ReadCallback readCallback, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo) - : OOPStackUnwinder(readCallback, getAllocatedBuffer, getStackWalkInfo) + OOPStackUnwinderArm64(ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) + : OOPStackUnwinder(readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext) { } #endif // FEATURE_CDAC_UNWINDER diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index 2087867d4c29a9..ba322b4e86e59f 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -32,7 +32,7 @@ HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, #ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); #else // FEATURE_CDAC_UNWINDER - getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); + getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, callbackContext); #endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } @@ -56,10 +56,10 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres _Out_writes_(cbBuffer) PVOID pBuffer, DWORD cbBuffer) { - // if (cbBuffer < sizeof(T_RUNTIME_FUNCTION)) - // { - // return E_INVALIDARG; - // } + if (cbBuffer < sizeof(T_RUNTIME_FUNCTION)) + { + return E_INVALIDARG; + } PVOID pFuncEntry = NULL; #ifndef FEATURE_CDAC_UNWINDER @@ -72,12 +72,12 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres memcpy(pBuffer, pFuncEntry, cbBuffer); return S_OK; #else // FEATURE_CDAC_UNWINDER - getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry)); + getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry), callbackContext); if (pFuncEntry == NULL) { return E_FAIL; } - if (readCallback((DWORD64)pFuncEntry, pBuffer, cbBuffer) != S_OK) + if (readFromTarget((DWORD64)pFuncEntry, pBuffer, cbBuffer, callbackContext) != S_OK) { return E_FAIL; } diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 1bd9ab5845d4a1..3e809e0aa759ce 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -7,9 +7,9 @@ #define __unwinder_h__ #ifdef FEATURE_CDAC_UNWINDER -using ReadCallback = int (*)(uint64_t addr, void* pBuffer, int bufferSize); -using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer); -using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry); +using ReadFromTarget = int (*)(uint64_t addr, void* pBuffer, int bufferSize, void* callbackContext); +using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer, void* callbackContext); +using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry, void* callbackContext); #endif // FEATURE_CDAC_UNWINDER //--------------------------------------------------------------------------------------- @@ -33,33 +33,37 @@ class OOPStackUnwinder // Given a control PC, return the base of the module it is in. For jitted managed code, this is the // start of the code heap. - HRESULT GetModuleBase(uint64_t address, + HRESULT GetModuleBase( DWORD64 address, _Out_ PDWORD64 pdwBase); // Given a control PC, return the function entry of the functoin it is in. - HRESULT GetFunctionEntry( DWORD64 address, + HRESULT GetFunctionEntry( DWORD64 address, _Out_writes_(cbBuffer) PVOID pBuffer, DWORD cbBuffer); #ifdef FEATURE_CDAC_UNWINDER protected: - OOPStackUnwinder(ReadCallback readCallback, + OOPStackUnwinder(ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, - GetStackWalkInfo getStackWalkInfo) - : readCallback(readCallback), + GetStackWalkInfo getStackWalkInfo, + void* callbackContext) + : readFromTarget(readFromTarget), getAllocatedBuffer(getAllocatedBuffer), - getStackWalkInfo(getStackWalkInfo) + getStackWalkInfo(getStackWalkInfo), + callbackContext(callbackContext) { } public: - // These functions are marked public because they are called using + // These functions pointers are marked public because they are called using // a global instance of OOPStackUnwinder in the ARM64 implementation. - ReadCallback readCallback; + ReadFromTarget readFromTarget; GetAllocatedBuffer getAllocatedBuffer; GetStackWalkInfo getStackWalkInfo; + void* callbackContext; + #endif // FEATURE_CDAC_UWNINDER }; diff --git a/src/coreclr/vm/amd64/gmscpu.h b/src/coreclr/vm/amd64/gmscpu.h index 5f2c483a16dabf..411f1cf0c71b88 100644 --- a/src/coreclr/vm/amd64/gmscpu.h +++ b/src/coreclr/vm/amd64/gmscpu.h @@ -115,16 +115,6 @@ struct LazyMachState : public MachState // ULONG64 m_CaptureRip; ULONG64 m_CaptureRsp; - - friend struct ::cdac_data; -}; - -template<> -struct cdac_data -{ - static constexpr size_t InstructionPointer = offsetof(LazyMachState, m_Rip); - static constexpr size_t StackPointer = offsetof(LazyMachState, m_Rsp); - static constexpr size_t ReturnAddress = offsetof(LazyMachState, _pRetAddr); }; inline void LazyMachState::setLazyStateFromUnwind(MachState* copy) diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 2bdb441723af3d..87922b6ef13ed8 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -1460,20 +1460,10 @@ class HelperMethodFrame : public Frame LazyMachState m_MachState; // pRetAddr points to the return address and the stack arguments - friend struct ::cdac_data; - // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame) }; -template<> -struct cdac_data -{ - static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame, m_Attribs); - static constexpr size_t FCallEntry = offsetof(HelperMethodFrame, m_FCallEntry); - static constexpr size_t LazyMachState = offsetof(HelperMethodFrame, m_MachState); -}; - // Restores registers saved in m_MachState EXTERN_C int __fastcall HelperMethodFrameRestoreState( INDEBUG_COMMA(HelperMethodFrame *pFrame) @@ -1555,20 +1545,10 @@ class HelperMethodFrame_1OBJ : public HelperMethodFrame private: PTR_OBJECTREF gcPtrs[1]; - friend struct ::cdac_data; - // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_1OBJ) }; -template<> -struct cdac_data -{ - static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_1OBJ, m_Attribs); - static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_1OBJ, m_FCallEntry); - static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_1OBJ, m_MachState); -}; - //----------------------------------------------------------------------------- // HelperMethodFrame_2OBJ @@ -1626,20 +1606,10 @@ class HelperMethodFrame_2OBJ : public HelperMethodFrame private: PTR_OBJECTREF gcPtrs[2]; - friend struct ::cdac_data; - // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_2OBJ) }; -template<> -struct cdac_data -{ - static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_2OBJ, m_Attribs); - static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_2OBJ, m_FCallEntry); - static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_2OBJ, m_MachState); -}; - //----------------------------------------------------------------------------- // HelperMethodFrame_3OBJ //----------------------------------------------------------------------------- @@ -1702,20 +1672,10 @@ class HelperMethodFrame_3OBJ : public HelperMethodFrame private: PTR_OBJECTREF gcPtrs[3]; - friend struct ::cdac_data; - // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_3OBJ) }; -template<> -struct cdac_data -{ - static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_3OBJ, m_Attribs); - static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_3OBJ, m_FCallEntry); - static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_3OBJ, m_MachState); -}; - //----------------------------------------------------------------------------- // HelperMethodFrame_PROTECTOBJ @@ -1778,20 +1738,10 @@ class HelperMethodFrame_PROTECTOBJ : public HelperMethodFrame PTR_OBJECTREF m_pObjRefs; UINT m_numObjRefs; - friend struct ::cdac_data; - // Keep as last entry in class DEFINE_VTABLE_GETTER_AND_CTOR_AND_DTOR(HelperMethodFrame_PROTECTOBJ) }; -template<> -struct cdac_data -{ - static constexpr size_t FrameAttributes = offsetof(HelperMethodFrame_PROTECTOBJ, m_Attribs); - static constexpr size_t FCallEntry = offsetof(HelperMethodFrame_PROTECTOBJ, m_FCallEntry); - static constexpr size_t LazyMachState = offsetof(HelperMethodFrame_PROTECTOBJ, m_MachState); -}; - class FramedMethodFrame : public TransitionFrame { VPTR_ABSTRACT_VTABLE_CLASS(FramedMethodFrame, TransitionFrame) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index b6d6c6367d44d3..b380918aaeb7f7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -5,24 +5,18 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -public interface IStackDataFrameHandle { }; public interface IStackWalkHandle { }; +public interface IStackDataFrameHandle { }; internal interface IStackWalk : IContract { static string IContract.Name => nameof(StackWalk); public virtual IStackWalkHandle CreateStackWalk(ThreadData threadData) => throw new NotImplementedException(); - public virtual bool Next(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); - public virtual IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); - public virtual byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); - public virtual TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); - - public virtual void Print(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); } internal struct StackWalk : IStackWalk diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 354c419666ceb9..8bd92ae8b9c17b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -33,7 +33,7 @@ public enum CorDebugPlatform : int CORDB_PLATFORM_POSIX_ARM = 10, CORDB_PLATFORM_POSIX_ARM64 = 11, CORDB_PLATFORM_POSIX_LOONGARCH64 = 12, - CORDB_PLATFORM_POSIX_RISCV64 = 12 + CORDB_PLATFORM_POSIX_RISCV64 = 13, } /// @@ -45,8 +45,21 @@ public enum CorDebugPlatform : int /// public abstract bool IsLittleEndian { get; } - public abstract int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); - public abstract int GetPlatform(out CorDebugPlatform platform); + /// + /// Platform of the target + /// + public abstract CorDebugPlatform Platform { get; } + + /// + /// Fills a buffer with the context of the given thread + /// + /// The identifier of the thread whose context is to be retrieved. The identifier is defined by the operating system. + /// A bitwise combination of platform-dependent flags that indicate which portions of the context should be read. + /// Size of . + /// Buffer filled with thread context. + /// HResult + public abstract int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer); + /// /// Reads a well-known global pointer value from the target process diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs index bb81a4cb68df3b..17ed2967deb7f9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerBase.EEJitManager.cs @@ -13,9 +13,11 @@ internal partial class ExecutionManagerBase : IExecutionManager private class EEJitManager : JitManager { private readonly INibbleMap _nibbleMap; + private readonly RuntimeFunctionLookup _runtimeFunctions; public EEJitManager(Target target, INibbleMap nibbleMap) : base(target) { _nibbleMap = nibbleMap; + _runtimeFunctions = RuntimeFunctionLookup.Create(target); } public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer jittedCodeAddress, [NotNullWhen(true)] out CodeBlock? info) @@ -58,10 +60,6 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) return TargetPointer.Null; - if (Target.GetTypeInfo(DataType.RuntimeFunction).Size is not uint runtimeFunctionSize) - { - throw new InvalidOperationException("Unable to get RuntimeFunction size"); - } if (realCodeHeader.NumUnwindInfos is not uint numUnwindInfos) { throw new InvalidOperationException("Unable to get NumUnwindInfos"); @@ -76,21 +74,15 @@ public override TargetPointer GetUnwindInfo(RangeSection rangeSection, TargetCod return TargetPointer.Null; } - ulong imageBase = rangeSection.Data.RangeBegin; - TargetPointer prevUnwindInfoAddress = unwindInfos; - TargetPointer currUnwindInfoAddress; - for (ulong i = 1; i < numUnwindInfos; i++) - { - currUnwindInfoAddress = unwindInfos + (i * runtimeFunctionSize); - Data.RuntimeFunction nextRuntimeFunction = Target.ProcessedData.GetOrAdd(currUnwindInfoAddress); - if (nextRuntimeFunction.BeginAddress + imageBase > jittedCodeAddress.Value) - { - return prevUnwindInfoAddress; - } - prevUnwindInfoAddress = currUnwindInfoAddress; - } + // Find the relative address that we are looking for + TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target); + TargetPointer imageBase = rangeSection.Data.RangeBegin; + TargetPointer relativeAddr = addr - imageBase; + + if (!_runtimeFunctions.TryGetRuntimeFunctionIndexForAddress(unwindInfos, numUnwindInfos, relativeAddr, out uint index)) + return TargetPointer.Null; - return prevUnwindInfoAddress; + return _runtimeFunctions.GetRuntimeFunctionAddress(unwindInfos, index); } private TargetPointer FindMethodCode(RangeSection rangeSection, TargetCodePointer jittedCodeAddress) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs index 6db089a81dcb11..597cc6db205124 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/Helpers/RuntimeFunctionLookup.cs @@ -54,18 +54,16 @@ bool Compare(uint index) bool Match(uint index) { - // Entries are terminated by a sentinel value of -1, so we can index one past the end safely. - // Read as a runtime function, its begin address is 0xffffffff (always > relative address). - // See RuntimeFunctionsTableNode.GetData in RuntimeFunctionsTableNode.cs - Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, index + 1); - if (relativeAddress >= nextFunc.BeginAddress) - return false; + // If there is a next Unwind Info, check if the address is in the next Unwind Info. + if (index < numRuntimeFunctions - 1) + { + Data.RuntimeFunction nextFunc = GetRuntimeFunction(runtimeFunctions, index + 1); + if (relativeAddress >= nextFunc.BeginAddress) + return false; + } Data.RuntimeFunction func = GetRuntimeFunction(runtimeFunctions, index); - if (relativeAddress >= func.BeginAddress) - return true; - - return false; + return relativeAddress >= func.BeginAddress; } } 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 index e8cfa5f6854f19..dcf0c0ff77a338 100644 --- 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 @@ -16,6 +16,7 @@ internal sealed class FrameIterator private readonly Target target; private readonly TargetPointer terminator; private TargetPointer currentFramePointer; + internal Data.Frame CurrentFrame => target.ProcessedData.GetOrAdd(currentFramePointer); public TargetPointer CurrentFrameAddress => currentFramePointer; @@ -41,66 +42,27 @@ public bool Next() return true; } - public bool TryUpdateContext(ref IContext context) { - return TryUpdateContext(target, CurrentFrame, ref context); - } - - public static IEnumerable EnumerateFrames(Target target, TargetPointer framePointer) - { - TargetPointer terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); - - while (framePointer != terminator) - { - Data.Frame frame = target.ProcessedData.GetOrAdd(framePointer); - yield return frame; - framePointer = frame.Next; - } - } - public static bool TryUpdateContext(Target target, Data.Frame frame, ref IContext context) - { - switch (frame.Type) + switch (CurrentFrame.Type) { case DataType.InlinedCallFrame: - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); + 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(frame.Address); + Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); return true; - case DataType.HelperMethodFrame: - case DataType.HelperMethodFrame_1OBJ: - case DataType.HelperMethodFrame_2OBJ: - case DataType.HelperMethodFrame_3OBJ: - case DataType.HelperMethodFrame_PROTECTOBJ: - Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(frame.Address); - if (helperMethodFrame.LazyMachState.StackPointer is null || helperMethodFrame.LazyMachState.InstructionPointer is null) - { - return false; - } - context.Clear(); - if (helperMethodFrame.LazyMachState.InstructionPointer is TargetPointer ip) - { - context.InstructionPointer = ip; - } - if (helperMethodFrame.LazyMachState.StackPointer is TargetPointer sp) - { - context.StackPointer = sp; - } - return true; default: - Console.WriteLine($"Unable to parse frame further: {frame.Type}"); - break; + return false; } - return false; } - public bool IsInlinedWithActiveCall() + public bool IsInlineCallFrameWithActiveCall() { if (CurrentFrame.Type != DataType.InlinedCallFrame) { @@ -109,55 +71,4 @@ public bool IsInlinedWithActiveCall() Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(currentFramePointer); return inlinedCallFrame.CallerReturnAddress != 0; } - - public static void PrintFrame(Target target, Data.Frame frame) - { - switch (frame.Type) - { - case DataType.InlinedCallFrame: - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(frame.Address); - Print(inlinedCallFrame); - break; - case DataType.SoftwareExceptionFrame: - Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(frame.Address); - Print(target, softwareExceptionFrame); - break; - case DataType.HelperMethodFrame: - case DataType.HelperMethodFrame_1OBJ: - case DataType.HelperMethodFrame_2OBJ: - case DataType.HelperMethodFrame_3OBJ: - case DataType.HelperMethodFrame_PROTECTOBJ: - Data.HelperMethodFrame helperMethodFrame = target.ProcessedData.GetOrAdd(frame.Address); - Print(helperMethodFrame); - break; - default: - Console.WriteLine($"Unable to parse frame further: {frame.Type}"); - break; - } - } - - public static void Print(InlinedCallFrame inlinedCallFrame) - { - Console.WriteLine($"[{nameof(InlinedCallFrame),-30}: Address={inlinedCallFrame.Address} IP={inlinedCallFrame.CallerReturnAddress}, SP={inlinedCallFrame.CallSiteSP}, FP={inlinedCallFrame.CalleeSavedFP}]"); - } - - public static void Print(Target target, SoftwareExceptionFrame softwareExceptionFrame) - { - IContext context = IContext.GetContextForPlatform(target); - context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); - Console.WriteLine($"[{nameof(SoftwareExceptionFrame),-30}: Address={softwareExceptionFrame.Address} IP={context.InstructionPointer.Value:x16}, SP={context.StackPointer.Value:x16}, FP={context.FramePointer.Value:x16}]"); - } - - public static void Print(HelperMethodFrame helperMethodFrame) - { - bool isValid = helperMethodFrame.LazyMachState.ReturnAddress != 0; - if (isValid) - { - Console.WriteLine($"[{helperMethodFrame.Type}: IP={helperMethodFrame.LazyMachState.InstructionPointer}, SP={helperMethodFrame.LazyMachState.StackPointer}, RA={helperMethodFrame.LazyMachState.ReturnAddress}]"); - } - else - { - Console.WriteLine($"[{helperMethodFrame.Type}: IP={helperMethodFrame.LazyMachState.InstructionPointer}, Invalid LazyMachState]"); - } - } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs index 7bcfa77a2c9dbf..25214d1601e790 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs @@ -29,8 +29,7 @@ public unsafe void ReadFromAddress(Target target, TargetPointer address) public static IContext GetContextForPlatform(Target target) { - target.GetPlatform(out Target.CorDebugPlatform platform); - switch (platform) + switch (target.Platform) { case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: @@ -42,7 +41,7 @@ public static IContext GetContextForPlatform(Target target) ARM64Context arm64Context = default; return arm64Context; default: - throw new ArgumentOutOfRangeException(nameof(platform), platform, null); + throw new InvalidOperationException($"Unsupported platform {target.Platform}"); } } } 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 75cad57ccf50b5..ad0308c77e5105 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 @@ -110,7 +110,7 @@ bool IStackWalk.Next(IStackWalkHandle stackWalkHandle) break; case StackWalkState.SW_FRAME: handle.frameIter.TryUpdateContext(ref handle.context); - if (!handle.frameIter.IsInlinedWithActiveCall()) + if (!handle.frameIter.IsInlineCallFrameWithActiveCall()) { handle.frameIter.Next(); } @@ -169,29 +169,6 @@ TargetPointer IStackWalk.GetFrameAddress(IStackDataFrameHandle stackDataFrameHan return TargetPointer.Null; } - void IStackWalk.Print(IStackWalkHandle stackWalkHandle) - { - StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); - IExecutionManager eman = _target.Contracts.ExecutionManager; - - TargetCodePointer ip = CodePointerUtils.CodePointerFromAddress(handle.context.InstructionPointer, _target); - if (eman.GetCodeBlockHandle(ip) is CodeBlockHandle cbh) - { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - Console.WriteLine($"[{handle.context.GetType().Name}] State={handle.state,-20} SP={handle.context.StackPointer.Value:x16} IP={handle.context.InstructionPointer.Value:x16} MethodDesc={methodDesc.Value:x16} BaseAddress={moduleBase.Value:x16}"); - } - else - { - Console.WriteLine($"[{handle.context.GetType().Name}] State={handle.state,-20} SP={handle.context.StackPointer.Value:x16} IP={handle.context.InstructionPointer.Value:x16} Unmanaged"); - } - - if (handle.frameIter.IsValid()) - { - FrameIterator.PrintFrame(_target, handle.frameIter.CurrentFrame); - } - } - private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle? codeBlockHandle) { IExecutionManager eman = _target.Contracts.ExecutionManager; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs index 008688dba96276..52a8581b190a41 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs @@ -9,150 +9,136 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal static unsafe partial class Unwinder { - // ReadCallback allows the unwinder to read memory from the target process - // into an allocated buffer. This buffer is either allocated by the unwinder - // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. - // In the latter case, the unwinder can only use the buffer for the duration of the - // unwind call. Once the call is over the cDAC will free all allocated buffers. - public delegate int ReadCallback(ulong address, void* buffer, int bufferSize); - public delegate int GetAllocatedBuffer(int bufferSize, void** buffer); - - // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp - // To maintain the same signature as the original function, we return void. - // If the moduleBase or funcEntry can not be found, both will be 0. - public delegate void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry); - [LibraryImport("unwinder_cdac_arm64", EntryPoint = "arm64Unwind")] private static partial int ARM64Unwind( ref ARM64Context context, - [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, - [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, - [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + delegate* unmanaged readFromTarget, + delegate* unmanaged getAllocatedBuffer, + delegate* unmanaged getStackWalkInfo, + void* callbackContext); + + public static int ARM64Unwind( + ref ARM64Context context, + Target target) + { + using CallbackContext callbackContext = new(target); + + GCHandle handle = GCHandle.Alloc(callbackContext); + int ret = ARM64Unwind(ref context, &ReadFromTarget, &GetAllocatedBuffer, &GetStackWalkInfo, GCHandle.ToIntPtr(handle).ToPointer()); + handle.Free(); + + return ret; + } [LibraryImport("unwinder_cdac_amd64", EntryPoint = "amd64Unwind")] private static partial int AMD64Unwind( ref AMD64Context context, - [MarshalAs(UnmanagedType.FunctionPtr)] ReadCallback readCallback, - [MarshalAs(UnmanagedType.FunctionPtr)] GetAllocatedBuffer getAllocatedBuffer, - [MarshalAs(UnmanagedType.FunctionPtr)] GetStackWalkInfo getStackWalkInfo); + delegate* unmanaged readFromTarget, + delegate* unmanaged getAllocatedBuffer, + delegate* unmanaged getStackWalkInfo, + void* callbackContext); public static int AMD64Unwind( ref AMD64Context context, Target target) { - ReadCallback readCallback; - GetAllocatedBuffer getAllocatedBuffer; - GetStackWalkInfo getStackWalkInfo; + using CallbackContext callbackContext = new(target); - // Move to IDisposable for freeing - List allocatedRegions = []; + GCHandle handle = GCHandle.Alloc(callbackContext); + int ret = AMD64Unwind(ref context, &ReadFromTarget, &GetAllocatedBuffer, &GetStackWalkInfo, GCHandle.ToIntPtr(handle).ToPointer()); + handle.Free(); - readCallback = (address, pBuffer, bufferSize) => - { - Span span = new Span(pBuffer, bufferSize); - target.ReadBuffer(address, span); - return 0; - }; - getAllocatedBuffer = (bufferSize, ppBuffer) => - { - *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); - IntPtr pBuffer = new(*ppBuffer); - //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); - allocatedRegions.Add(pBuffer); - return 0; - }; - getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => + return ret; + } + + private sealed class CallbackContext(Target target) : IDisposable + { + private bool disposed; + public Target Target { get; } = target; + public List AllocatedRegions { get; } = []; + + public void Dispose() { - IExecutionManager eman = target.Contracts.ExecutionManager; + Dispose(true); + GC.SuppressFinalize(this); + } - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + private void Dispose(bool disposing) + { + if (disposed) return; - try + if (disposing) { - if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) + foreach (IntPtr ptr in AllocatedRegions) { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; + NativeMemory.Free(ptr.ToPointer()); } } - catch (System.Exception ex) - { - Console.WriteLine($"GetStackWalkInfo failed: {ex}"); - } - }; - - int ret = AMD64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); + disposed = true; + } + } - foreach (IntPtr ptr in allocatedRegions) + // ReadFromTarget allows the unwinder to read memory from the target process + // into an allocated buffer. This buffer is either allocated by the unwinder + // with its lifetime managed by the unwinder or allocated through GetAllocatedBuffer. + // In the latter case, the unwinder can only use the buffer for the duration of the + // unwind call. Once the call is over the cDAC will free all allocated buffers. + [UnmanagedCallersOnly] + private static unsafe int ReadFromTarget(ulong address, void* pBuffer, int bufferSize, void* context) + { + if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) { - //Console.WriteLine($"Freeing buffer at {ptr:x16}"); - NativeMemory.Free(ptr.ToPointer()); + return -1; } - - return ret; + Span span = new Span(pBuffer, bufferSize); + callbackContext.Target.ReadBuffer(address, span); + return 0; } - public static int ARM64Unwind( - ref ARM64Context context, - Target target) + // GetAllocatedBuffer allows the unwinder to allocate a buffer that will be freed + // once the unwinder call is complete. + // Freeing is handeled in the Dispose method of CallbackContext. + [UnmanagedCallersOnly] + private static unsafe int GetAllocatedBuffer(int bufferSize, void** ppBuffer, void* context) { - ReadCallback readCallback; - GetAllocatedBuffer getAllocatedBuffer; - GetStackWalkInfo getStackWalkInfo; + if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) + { + return -1; + } + *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); + callbackContext.AllocatedRegions.Add((IntPtr)(*ppBuffer)); + return 0; + } - // Move to IDisposable for freeing - List allocatedRegions = []; + // cDAC version of GetRuntimeStackWalkInfo defined in codeman.cpp + // To maintain the same signature as the original function, this returns void. + // If the moduleBase or funcEntry can not be found, both will be 0. + [UnmanagedCallersOnly] + private static unsafe void GetStackWalkInfo(ulong controlPC, void* pModuleBase, void* pFuncEntry, void* context) + { + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; - readCallback = (address, pBuffer, bufferSize) => - { - Span span = new Span(pBuffer, bufferSize); - target.ReadBuffer(address, span); - return 0; - }; - getAllocatedBuffer = (bufferSize, ppBuffer) => + if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) { - *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); - IntPtr pBuffer = new(*ppBuffer); - //Console.WriteLine($"Allocating buffer at {pBuffer:x16}"); - allocatedRegions.Add(pBuffer); - return 0; - }; - getStackWalkInfo = (controlPC, pModuleBase, pFuncEntry) => - { - IExecutionManager eman = target.Contracts.ExecutionManager; - - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = 0; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; + return; + } - try - { - if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) - { - TargetPointer methodDesc = eman.GetMethodDesc(cbh); - TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); - TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); - if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; - if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; - } - } - catch (System.Exception ex) + IExecutionManager eman = callbackContext.Target.Contracts.ExecutionManager; + try + { + if (eman.GetCodeBlockHandle(controlPC) is CodeBlockHandle cbh) { - Console.WriteLine($"GetStackWalkInfo failed: {ex}"); + TargetPointer methodDesc = eman.GetMethodDesc(cbh); + TargetPointer moduleBase = eman.GetModuleBaseAddress(cbh); + TargetPointer unwindInfo = eman.GetUnwindInfo(cbh, controlPC); + if ((nuint)pModuleBase != 0) *(nuint*)pModuleBase = (nuint)moduleBase.Value; + if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = (nuint)unwindInfo.Value; } - }; - - int ret = ARM64Unwind(ref context, readCallback, getAllocatedBuffer, getStackWalkInfo); - - foreach (IntPtr ptr in allocatedRegions) + } + catch (System.Exception ex) { - //Console.WriteLine($"Freeing buffer at {ptr:x16}"); - NativeMemory.Free(ptr.ToPointer()); + Console.WriteLine($"GetStackWalkInfo failed: {ex}"); } - - return ret; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 5fe086a16400bd..796eb89dffce71 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; 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 7213f4601d30dd..f491cafc14875f 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 @@ -10,11 +10,6 @@ internal sealed class Frame : IData { private static readonly List SupportedFrameTypes = [ DataType.InlinedCallFrame, - DataType.HelperMethodFrame, - DataType.HelperMethodFrame_1OBJ, - DataType.HelperMethodFrame_2OBJ, - DataType.HelperMethodFrame_3OBJ, - DataType.HelperMethodFrame_PROTECTOBJ, DataType.SoftwareExceptionFrame, DataType.ResumableFrame, @@ -42,6 +37,11 @@ internal sealed class Frame : IData DataType.TailCallFrame, DataType.ExceptionFilterFrame, DataType.AssumeByrefFromJITStack, + DataType.HelperMethodFrame, + DataType.HelperMethodFrame_1OBJ, + DataType.HelperMethodFrame_2OBJ, + DataType.HelperMethodFrame_3OBJ, + DataType.HelperMethodFrame_PROTECTOBJ, ]; static Frame IData.Create(Target target, TargetPointer address) @@ -66,14 +66,13 @@ private static DataType FindType(Target target, TargetPointer address) { // not all Frames are in all builds, so we need to catch the exception typeVptr = target.ReadGlobalPointer(frameType.ToString() + "VPtr"); + if (instanceVptr == typeVptr) + { + return frameType; + } } catch (InvalidOperationException) { - continue; - } - if (instanceVptr == typeVptr) - { - return frameType; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs deleted file mode 100644 index 22e1d8d433d2f2..00000000000000 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HelperMethodFrame.cs +++ /dev/null @@ -1,53 +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 System.Collections.Generic; -using System.Diagnostics; - -namespace Microsoft.Diagnostics.DataContractReader.Data; - -internal class HelperMethodFrame : IData -{ - private static readonly List HelperMethodFrameTypes = [ - "HelperMethodFrame", - "HelperMethodFrame_1OBJ", - "HelperMethodFrame_2OBJ", - "HelperMethodFrame_3OBJ", - "HelperMethodFrame_PROTECTOBJ", - ]; - - private static DataType FindType(Target target, TargetPointer address) - { - TargetPointer instanceVptr = target.ReadPointer(address); - - foreach (string frameTypeName in HelperMethodFrameTypes) - { - TargetPointer typeVptr = target.ReadGlobalPointer(frameTypeName + "VPtr"); - if (instanceVptr == typeVptr) - { - return Enum.TryParse(frameTypeName, out DataType type) ? type : DataType.Unknown; - } - } - - return DataType.Unknown; - } - - static HelperMethodFrame IData.Create(Target target, TargetPointer address) - => new HelperMethodFrame(target, address); - - public HelperMethodFrame(Target target, TargetPointer address) - { - Type = FindType(target, address); - Debug.Assert(Type != DataType.Unknown); - Target.TypeInfo type = target.GetTypeInfo(Type); - FrameAttributes = target.Read(address + (ulong)type.Fields[nameof(FrameAttributes)].Offset); - FCallEntry = target.ReadPointer(address + (ulong)type.Fields[nameof(FCallEntry)].Offset); - LazyMachState = target.ProcessedData.GetOrAdd(address + (ulong)type.Fields[nameof(LazyMachState)].Offset); - } - - public DataType Type { get; } - public uint FrameAttributes { get; } - public TargetPointer FCallEntry { get; } - public LazyMachState LazyMachState { get; } -} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs deleted file mode 100644 index 867ef7890e1aa9..00000000000000 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/LazyMachState.cs +++ /dev/null @@ -1,34 +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 System.Collections.Generic; - -namespace Microsoft.Diagnostics.DataContractReader.Data; - -internal class LazyMachState : IData -{ - static LazyMachState IData.Create(Target target, TargetPointer address) - => new LazyMachState(target, address); - - public LazyMachState(Target target, TargetPointer address) - { - Target.TypeInfo type = target.GetTypeInfo(DataType.LazyMachState); - if (type.Fields.ContainsKey(nameof(InstructionPointer))) - { - InstructionPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(InstructionPointer)].Offset); - } - if (type.Fields.ContainsKey(nameof(StackPointer))) - { - StackPointer = target.ReadPointer(address + (ulong)type.Fields[nameof(StackPointer)].Offset); - } - if (type.Fields.ContainsKey(nameof(ReturnAddress))) - { - ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); - } - } - - public TargetPointer? InstructionPointer { get; } - public TargetPointer? StackPointer { get; } - public TargetPointer? ReturnAddress { get; } -} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs index 9de354ae54dcb3..e318aa2638f58f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs @@ -11,14 +11,14 @@ static SoftwareExceptionFrame IData.Create(Target target public SoftwareExceptionFrame(Target target, TargetPointer address) { // TypeInfo will only exist if FEATURE_EH_FUNCLETS is enabled. - // If it doesn't exist, then this type of frame is not present. + // If it doesn't exist, then this type of frame is not present in target. Target.TypeInfo type = target.GetTypeInfo(DataType.SoftwareExceptionFrame); Address = address; TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); } - public TargetPointer Address { get;} + public TargetPointer Address { get; } public TargetPointer TargetContext { get; } public TargetPointer ReturnAddress { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs index 481b8eccf32151..84ffab173b5f6c 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/RealCodeHeader.cs @@ -13,9 +13,11 @@ public RealCodeHeader(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.RealCodeHeader); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); + // Only available if FEATURE_EH_FUNCLETS is enabled. if (type.Fields.ContainsKey(nameof(NumUnwindInfos))) NumUnwindInfos = target.Read(address + (ulong)type.Fields[nameof(NumUnwindInfos)].Offset); + // Only available if FEATURE_EH_FUNCLETS is enabled. if (type.Fields.ContainsKey(nameof(UnwindInfos))) UnwindInfos = address + (ulong)type.Fields[nameof(UnwindInfos)].Offset; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index be495b2c6a4aac..11673872552382 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -44,19 +44,16 @@ private readonly struct Configuration public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); - public delegate int GetTargetPlatform(out int platform); - - public GetTargetThreadContextDelegate? getThreadContext; - public GetTargetPlatform? getTargetPlatform; + public delegate int GetTargetPlatformDelegate(out int platform); public static bool TryCreate( ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatform getTargetPlatform, + GetTargetPlatformDelegate getTargetPlatform, out ContractDescriptorTarget? target) { - Reader reader = new Reader(readFromTarget); + Reader reader = new Reader(readFromTarget, getThreadContext, getTargetPlatform); if (TryReadContractDescriptor( contractDescriptor, reader, @@ -65,8 +62,6 @@ public static bool TryCreate( out TargetPointer[] pointerData)) { target = new ContractDescriptorTarget(config, descriptor!, pointerData, reader); - target.getThreadContext = getThreadContext; - target.getTargetPlatform = getTargetPlatform; return true; } @@ -83,6 +78,12 @@ private ContractDescriptorTarget(Configuration config, ContractDescriptorParser. _contracts = descriptor.Contracts ?? []; + if (_reader.GetTargetPlatform(out int platform) < 0) + { + throw new InvalidOperationException($"Unable to read target platform."); + } + Platform = (CorDebugPlatform)platform; + // Read types and map to known data types if (descriptor.Types is not null) { @@ -229,23 +230,11 @@ private static DataType GetDataType(string type) public override int PointerSize => _config.PointerSize; public override bool IsLittleEndian => _config.IsLittleEndian; + public override CorDebugPlatform Platform { get; } - public override int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill) - { - if (getThreadContext is null) - throw new InvalidOperationException("GetThreadContext is not available"); - - int hr = getThreadContext(threadId, contextFlags, contextSize, bufferToFill); - return hr; - } - - public override int GetPlatform(out CorDebugPlatform platform) + public override int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer) { - if (getTargetPlatform is null) - throw new InvalidOperationException("GetTargetPlatform is not available"); - int hr = getTargetPlatform(out int platformInt); - platform = (CorDebugPlatform)platformInt; - return hr; + return _reader.GetThreadContext(threadId, contextFlags, contextSize, buffer); } /// @@ -561,7 +550,10 @@ public void Clear() } } - private readonly struct Reader(ReadFromTargetDelegate readFromTarget) + private readonly struct Reader( + ReadFromTargetDelegate readFromTarget, + GetTargetThreadContextDelegate getThreadContext, + GetTargetPlatformDelegate getTargetPlatform) { public int ReadFromTarget(ulong address, Span buffer) { @@ -570,5 +562,15 @@ public int ReadFromTarget(ulong address, Span buffer) public int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead) => readFromTarget(address, new Span(buffer, checked((int)bytesToRead))); + + public int GetTargetPlatform(out int platform) + { + return getTargetPlatform(out platform); + } + + public int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer) + { + return getThreadContext(threadId, contextFlags, contextSize, buffer); + } } } diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 3e4bf5806ca074..9bf5ddb409090e 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -13,7 +13,8 @@ extern "C" // descriptor: the address of the descriptor in the target process // read_from_target: a callback that reads memory from the target process // read_thread_context: a callback that reads the context of a thread in the target process -// read_context: a context pointer that will be passed to read_from_target +// get_platform: a callback that reads the platform of the target process +// read_context: a context pointer that will be passed to callbacks // handle: returned opaque the handle to the reader. This should be passed to other functions in this API. int cdac_reader_init( uint64_t descriptor, diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index 313bf09995d47e..56e814ab540fce 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; @@ -68,11 +67,6 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* #endif return HResults.S_OK; - - // if (_legacyImpl is not null) - // { - // _legacyImpl.GetContext(contextFlags, contextBufSize, contextSize, contextBuf); - // } } int IXCLRDataStackWalk.GetFrame(void** frame) @@ -105,10 +99,6 @@ int IXCLRDataStackWalk.Next() } int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) { - string outputhPath = "C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt"; - using StreamWriter writer = new StreamWriter(outputhPath, true); - Console.SetOut(writer); - const uint DACSTACKPRIV_REQUEST_FRAME_DATA = 0xf0000000; int hr = HResults.S_OK; @@ -143,13 +133,10 @@ int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, for (int i = 0; i < outBufferSize; i++) { - // Console.WriteLine($"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); Debug.Assert(localOutBuffer[i] == outBuffer[i], $"cDAC: {outBuffer[i]:x}, DAC: {localOutBuffer[i]:x}"); } } #endif - writer.Close(); - return hr; } int IXCLRDataStackWalk.SetContext(uint contextSize, [In, MarshalUsing(CountElementName = "contextSize")] byte[] context) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index a16817d57151e0..e4367f004b0d91 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -569,9 +569,7 @@ int ISOSDacInterface.GetMethodDescName(ulong methodDesc, uint count, char* name, int ISOSDacInterface.GetMethodDescPtrFromFrame(ulong frameAddr, ulong* ppMD) => _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromFrame(frameAddr, ppMD) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodDescPtrFromIP(ulong ip, ulong* ppMD) - { - return _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromIP(ip, ppMD) : HResults.E_NOTIMPL; - } + => _legacyImpl is not null ? _legacyImpl.GetMethodDescPtrFromIP(ip, ppMD) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodDescTransparencyData(ulong methodDesc, void* data) => _legacyImpl is not null ? _legacyImpl.GetMethodDescTransparencyData(methodDesc, data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetMethodTableData(ulong mt, DacpMethodTableData* data) diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index eb4549410a782c..c63c7234a91fb9 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -31,11 +31,11 @@ + - diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index 231af072fcc597..ff941454038e43 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -168,6 +168,13 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta throw new InvalidOperationException("Context already created"); ulong contractDescriptorAddress = CreateDescriptorFragments(); MockMemorySpace.ReadContext context = GetReadContext(); - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, null, out target); + ContractDescriptorTarget.GetTargetPlatformDelegate getTargetPlatform = (out int platform) => + { + platform = TargetTestHelpers.Arch.Is64Bit ? + (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64 : + (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_X86; + return 0; + }; + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, getTargetPlatform, out target); } } diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 43565a4efcf88e..8a60149a9fdb44 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -29,6 +29,7 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; + Platform = Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64; _contractRegistry = new Mock().Object; _dataCache = new DefaultDataCache(this); _typeInfoCache = types ?? []; @@ -43,6 +44,7 @@ internal void SetContracts(ContractRegistry contracts) public override int PointerSize { get; } public override bool IsLittleEndian { get; } + public override CorDebugPlatform Platform { get; } public override bool IsAlignedToPointerSize(TargetPointer pointer) { @@ -226,12 +228,7 @@ public override Target.TypeInfo GetTypeInfo(DataType dataType) throw new NotImplementedException(); } - public override int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill) - { - throw new InvalidOperationException("GetThreadContext is not available"); - } - - public override int GetPlatform(out CorDebugPlatform platform) => throw new NotImplementedException(); + public override int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill) => throw new NotImplementedException(); public override Target.IDataCache ProcessedData => _dataCache; public override ContractRegistry Contracts => _contractRegistry; From 831fb141e550afb642984a8233247d509b4e8ddd Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Feb 2025 18:19:48 -0500 Subject: [PATCH 13/63] fix issues caused by merge from origin/main --- .../Contracts/IStackWalk.cs | 4 ++-- .../Contracts/ExecutionManager/ExecutionManager_1.cs | 2 ++ .../Contracts/ExecutionManager/ExecutionManager_2.cs | 2 ++ .../Contracts/StackWalk/IContext.cs | 2 +- .../Contracts/StackWalk/StackWalkFactory.cs | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index b380918aaeb7f7..8b8a10decf0e43 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -8,7 +8,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; public interface IStackWalkHandle { }; public interface IStackDataFrameHandle { }; -internal interface IStackWalk : IContract +public interface IStackWalk : IContract { static string IContract.Name => nameof(StackWalk); @@ -19,7 +19,7 @@ internal interface IStackWalk : IContract public virtual TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); } -internal struct StackWalk : IStackWalk +public struct StackWalk : IStackWalk { // Everything throws NotImplementedException } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index ce9f6ce9ce5972..5b501a3118a380 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -17,4 +17,6 @@ internal ExecutionManager_1(Target target, Data.RangeSectionMap topRangeSectionM public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); + public TargetPointer GetModuleBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetModuleBaseAddress(codeInfoHandle); + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => _executionManagerCore.GetUnwindInfo(codeInfoHandle, ip); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index a935ca00f0a32b..b00c8143c67464 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -17,4 +17,6 @@ internal ExecutionManager_2(Target target, Data.RangeSectionMap topRangeSectionM public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle); public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle); + public TargetPointer GetModuleBaseAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetModuleBaseAddress(codeInfoHandle); + public TargetPointer GetUnwindInfo(CodeBlockHandle codeInfoHandle, TargetCodePointer ip) => _executionManagerCore.GetUnwindInfo(codeInfoHandle, ip); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs index 25214d1601e790..2fd6087e3bf8fc 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs @@ -6,7 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -internal interface IContext +public interface IContext { public abstract uint Size { get; } public abstract uint DefaultContextFlags { get; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs index 813372fef0ea7d..3a05fdb48ddc15 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalkFactory.cs @@ -5,7 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal sealed class StackWalkFactory : IContractFactory +public sealed class StackWalkFactory : IContractFactory { IStackWalk IContractFactory.CreateContract(Target target, int version) { From 4f97647268300ea0a1de40348089622e2ce8fabd Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Feb 2025 18:37:21 -0500 Subject: [PATCH 14/63] remove unused datatype --- .../DataType.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 05f6b0aa5ceedb..c97a76bdbbf778 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -85,16 +85,11 @@ public enum DataType Frame, InlinedCallFrame, - HelperMethodFrame, - HelperMethodFrame_1OBJ, - HelperMethodFrame_2OBJ, - HelperMethodFrame_3OBJ, - HelperMethodFrame_PROTECTOBJ, + SoftwareExceptionFrame, ResumableFrame, RedirectedTHreadFrame, FaultingExceptionFrame, - SoftwareExceptionFrame, FuncEvalFrame, UnmanagedToManagedFrame, ComMethodFrame, @@ -117,6 +112,9 @@ public enum DataType TailCallFrame, ExceptionFilterFrame, AssumeByrefFromJITStack, - - LazyMachState, + HelperMethodFrame, + HelperMethodFrame_1OBJ, + HelperMethodFrame_2OBJ, + HelperMethodFrame_3OBJ, + HelperMethodFrame_PROTECTOBJ, } From 721042c32e6e1af2b1494be8de02f98a27197f19 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 7 Feb 2025 12:50:28 -0500 Subject: [PATCH 15/63] only build native unwinders on windows amd64 --- src/coreclr/unwinder/CMakeLists.txt | 8 ++++++-- src/coreclr/unwinder/amd64/unwinder.cpp | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index 8dc7d36afd4a2a..54b97ae4176b97 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -86,5 +86,9 @@ function(create_platform_unwinder) target_compile_definitions(${TARGETDETAILS_TARGET} PRIVATE FEATURE_NO_HOST FEATURE_CDAC_UNWINDER) endfunction() -create_platform_unwinder(TARGET unwinder_cdac_amd64 ARCH x64 DESTINATIONS cdaclibs) -create_platform_unwinder(TARGET unwinder_cdac_arm64 ARCH arm64 DESTINATIONS cdaclibs) +# TODO: Support building cDAC unwinders on other platforms +# https://github.com/dotnet/runtime/issues/112272#issue-2838611496 +if(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) + create_platform_unwinder(TARGET unwinder_cdac_amd64 ARCH x64 DESTINATIONS cdaclibs) + create_platform_unwinder(TARGET unwinder_cdac_arm64 ARCH arm64 DESTINATIONS cdaclibs) +endif(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 38e40b133e218d..c85ded861f5b6b 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -308,6 +308,7 @@ class InstructionBuffer if (SUCCEEDED(hr)) { // TODO: Implement breakpoint patching for cDAC + // https://github.com/dotnet/runtime/issues/112273#issue-2838620747 // On X64, we need to replace any patches which are within the requested memory range. // This is because the X64 unwinder needs to disassemble the native instructions in order to determine From 2887c4c393d535465a8b811430ba66f5366322f5 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 7 Feb 2025 13:55:43 -0500 Subject: [PATCH 16/63] change cmakelists order --- src/coreclr/unwinder/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index 54b97ae4176b97..04bd87c1cccfbe 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -7,7 +7,6 @@ convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) if(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_wks OBJECT ${UNWINDER_SOURCES}) - add_dependencies(unwinder_wks eventing_headers) target_include_directories(unwinder_wks BEFORE PRIVATE ${VM_DIR}) target_include_directories(unwinder_wks BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) target_include_directories(unwinder_wks BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -17,10 +16,10 @@ if(CLR_CMAKE_HOST_UNIX) target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/gcdump) target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/debug/daccess) target_include_directories(unwinder_wks PRIVATE ${ARCH_SOURCES_DIR}) + add_dependencies(unwinder_wks eventing_headers) endif(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_dac ${UNWINDER_SOURCES}) -add_dependencies(unwinder_dac eventing_headers) target_include_directories(unwinder_dac BEFORE PRIVATE ${VM_DIR}) target_include_directories(unwinder_dac BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) target_include_directories(unwinder_dac BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -30,6 +29,7 @@ target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/gc) target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/gcdump) target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/debug/daccess) target_include_directories(unwinder_dac PRIVATE ${ARCH_SOURCES_DIR}) +add_dependencies(unwinder_dac eventing_headers) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) From 1df309324de18dce33f2abb63391f794438558a9 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 7 Feb 2025 13:59:51 -0500 Subject: [PATCH 17/63] remove eventing_header dependency for cdac_unwinders --- src/coreclr/unwinder/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index 04bd87c1cccfbe..05785877d3fb3c 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -63,8 +63,6 @@ function(create_platform_unwinder) ${UNWINDER_SOURCES} ) - add_dependencies(${TARGETDETAILS_TARGET} eventing_headers) - target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${VM_DIR}) target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) From 369683850d8ced22973bcdbd12fafbd303ad1a04 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 7 Feb 2025 19:18:30 -0500 Subject: [PATCH 18/63] fix loading cDAC on non-windows platforms --- src/coreclr/debug/daccess/cdac.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index dc04fdf99ccb73..01e92782aeef4d 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -26,10 +26,14 @@ namespace path.Truncate(iter); path.Append(CDAC_LIB_NAME); - // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR loads dependencies from the same - // directory as cdacreader.dll. Once the native portions of the cDAC +#ifdef HOST_WINDOWS + // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR tells the native windows loader to load dependencies + // from the same directory as cdacreader.dll. Once the native portions of the cDAC // are statically linked, this won't be required. *phCDAC = CLRLoadLibraryEx(path.GetUnicode(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); +#else // !HOST_WINDOWS + *phCDAC = CLRLoadLibrary(path.GetUnicode()); +#endif // HOST_WINDOWS if (*phCDAC == NULL) return false; From 373f9efe8d5617d98886aa5a8d7d0e51ebe18aee Mon Sep 17 00:00:00 2001 From: maxcharlamb <44248479+max-charlamb@users.noreply.github.com> Date: Sun, 9 Feb 2025 15:19:59 -0500 Subject: [PATCH 19/63] use unused variable --- src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index 56e814ab540fce..9979f205f68f3a 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -66,7 +66,7 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* } #endif - return HResults.S_OK; + return hr; } int IXCLRDataStackWalk.GetFrame(void** frame) From 5edb98db2205b26a77f0e50fb5f1996352ee5db7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 10 Feb 2025 12:48:46 -0500 Subject: [PATCH 20/63] move to testhost for library tests --- src/libraries/externals.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/externals.csproj b/src/libraries/externals.csproj index 4b12dc2dca1045..47e7c1867b0421 100644 --- a/src/libraries/externals.csproj +++ b/src/libraries/externals.csproj @@ -92,6 +92,8 @@ TODO: [cdac] Remove once cdacreader is added to shipping shared framework --> + + From a1703a018615e3b80ce9ded26453e5dbeca80ea9 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 10 Feb 2025 14:46:14 -0500 Subject: [PATCH 21/63] don't build cDAC in Libraries_WithPackages run --- eng/pipelines/runtime-official.yml | 2 +- eng/pipelines/runtime.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index 03c4308e13aebf..7b3afe0f222315 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -367,7 +367,7 @@ extends: - windows_x64 jobParameters: templatePath: 'templates-official' - buildArgs: -s tools+libs -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true + buildArgs: -s tools.illink+libs -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true nameSuffix: Libraries_WithPackages isOfficialBuild: ${{ variables.isOfficialBuild }} postBuildSteps: diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index f39bb89d3294fb..12734ac02e9142 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1229,7 +1229,7 @@ extends: platforms: - windows_x64 jobParameters: - buildArgs: -test -s tools+libs+libs.tests -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true + buildArgs: -test -s tools.illink+libs+libs.tests -pack -c $(_BuildConfig) /p:TestAssemblies=false /p:TestPackages=true nameSuffix: Libraries_WithPackages timeoutInMinutes: 150 condition: >- From 71d55906daec93caa0e86cf409053d37f19f4d51 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 10 Feb 2025 18:01:40 -0500 Subject: [PATCH 22/63] isolate context datastructures from logic --- .../StackWalk/{ => Context}/AMD64Context.cs | 78 +++---------------- .../StackWalk/{ => Context}/ARM64Context.cs | 63 +-------------- .../StackWalk/Context/ContextHolder.cs | 48 ++++++++++++ .../Contracts/StackWalk/Context/IContext.cs | 15 ++++ .../IPlatformAgnosticContext.cs} | 20 ++--- .../{ => Context}/RegisterAttribute.cs | 0 .../StackWalk/{ => Context}/Unwinder.cs | 0 .../Contracts/StackWalk/FrameIterator.cs | 8 +- .../Contracts/StackWalk/StackWalk_1.cs | 12 +-- .../cdacreader/src/Legacy/ClrDataStackWalk.cs | 4 +- 10 files changed, 91 insertions(+), 157 deletions(-) rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/{ => Context}/AMD64Context.cs (69%) rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/{ => Context}/ARM64Context.cs (75%) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/{IContext.cs => Context/IPlatformAgnosticContext.cs} (68%) rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/{ => Context}/RegisterAttribute.cs (100%) rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/{ => Context}/Unwinder.cs (100%) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs similarity index 69% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs index 3e78d57177e729..a56fc168d9b73b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/AMD64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Reflection; using System.Runtime.InteropServices; -using System.Text; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -12,25 +10,25 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// AMD64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -internal struct AMD64Context : IContext +internal struct AMD64Context : IPlatformContext { [Flags] public enum ContextFlagsValues : uint { CONTEXT_AMD = 0x00100000, - CONTEXT_CONTROL = CONTEXT_AMD | 0x00000001, - CONTEXT_INTEGER = CONTEXT_AMD | 0x00000002, - CONTEXT_SEGMENTS = CONTEXT_AMD | 0x00000004, - CONTEXT_FLOATING_POINT = CONTEXT_AMD | 0x00000008, - CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD | 0x00000010, + CONTEXT_CONTROL = CONTEXT_AMD | 0x1, + CONTEXT_INTEGER = CONTEXT_AMD | 0x2, + CONTEXT_SEGMENTS = CONTEXT_AMD | 0x4, + CONTEXT_FLOATING_POINT = CONTEXT_AMD | 0x8, + CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD | 0x10, CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, - CONTEXT_XSTATE = CONTEXT_AMD | 0x00000040, - CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x00000080, + CONTEXT_XSTATE = CONTEXT_AMD | 0x40, + CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x80, } - public uint Size => 0x4d0; - public uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + public readonly uint Size => 0x4d0; + public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public TargetPointer StackPointer { @@ -53,62 +51,6 @@ public void Unwind(Target target) Unwinder.AMD64Unwind(ref this, target); } - public unsafe void FillFromBuffer(Span buffer) - { - Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); - Span byteSpan = MemoryMarshal.Cast(structSpan); - if (buffer.Length > sizeof(AMD64Context)) - { - buffer.Slice(0, sizeof(AMD64Context)).CopyTo(byteSpan); - } - else - { - buffer.CopyTo(byteSpan); - } - } - - public unsafe byte[] GetBytes() - { - Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); - Span byteSpan = MemoryMarshal.AsBytes(structSpan); - return byteSpan.ToArray(); - } - - public IContext Clone() - { - AMD64Context clone = this; - return clone; - } - - public void Clear() - { - this = default; - } - - public override string ToString() - { - StringBuilder sb = new(); - foreach (FieldInfo fieldInfo in typeof(AMD64Context).GetFields()) - { - switch (fieldInfo.GetValue(this)) - { - case ulong v: - sb.AppendLine($"{fieldInfo.Name} = {v:x16}"); - break; - case uint v: - sb.AppendLine($"{fieldInfo.Name} = {v:x8}"); - break; - case ushort v: - sb.AppendLine($"{fieldInfo.Name} = {v:x4}"); - break; - default: - sb.AppendLine($"{fieldInfo.Name} = {fieldInfo.GetValue(this)}"); - continue; - } - } - return sb.ToString(); - } - [FieldOffset(0x0)] public ulong P1Home; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs similarity index 75% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs index 4e15c6583fb8e5..6be29853aa6f47 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ARM64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Reflection; using System.Runtime.InteropServices; -using System.Text; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -12,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// ARM64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -internal struct ARM64Context : IContext +internal struct ARM64Context : IPlatformContext { [Flags] public enum ContextFlagsValues : uint @@ -27,9 +25,9 @@ public enum ContextFlagsValues : uint CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, } - public uint Size => 0x390; + public readonly uint Size => 0x390; - public uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public TargetPointer StackPointer { @@ -52,61 +50,6 @@ public void Unwind(Target target) Unwinder.ARM64Unwind(ref this, target); } - public void Clear() - { - this = default; - } - public unsafe void FillFromBuffer(Span buffer) - { - Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); - Span byteSpan = MemoryMarshal.Cast(structSpan); - if (buffer.Length > sizeof(ARM64Context)) - { - buffer.Slice(0, sizeof(ARM64Context)).CopyTo(byteSpan); - } - else - { - buffer.CopyTo(byteSpan); - } - } - - public unsafe byte[] GetBytes() - { - Span structSpan = MemoryMarshal.CreateSpan(ref this, 1); - Span byteSpan = MemoryMarshal.AsBytes(structSpan); - return byteSpan.ToArray(); - } - - public IContext Clone() - { - ARM64Context clone = this; - return clone; - } - - public override string ToString() - { - StringBuilder sb = new(); - foreach (FieldInfo fieldInfo in typeof(ARM64Context).GetFields()) - { - switch (fieldInfo.GetValue(this)) - { - case ulong v: - sb.AppendLine($"{fieldInfo.Name} = {v:x16}"); - break; - case uint v: - sb.AppendLine($"{fieldInfo.Name} = {v:x8}"); - break; - case ushort v: - sb.AppendLine($"{fieldInfo.Name} = {v:x4}"); - break; - default: - sb.AppendLine($"{fieldInfo.Name} = {fieldInfo.GetValue(this)}"); - continue; - } - } - return sb.ToString(); - } - // Control flags [FieldOffset(0x0)] 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 new file mode 100644 index 00000000000000..40d702a9b91f6e --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs @@ -0,0 +1,48 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +public class ContnextHolder : IPlatformAgnosticContext where T : struct, IPlatformContext +{ + public T Context; + + public uint Size => Context.Size; + public uint DefaultContextFlags => Context.DefaultContextFlags; + + public TargetPointer StackPointer { get => Context.StackPointer; set => Context.StackPointer = value; } + public TargetPointer InstructionPointer { get => Context.InstructionPointer; set => Context.InstructionPointer = value; } + public TargetPointer FramePointer { get => Context.FramePointer; set => Context.FramePointer = value; } + + public unsafe void ReadFromAddress(Target target, TargetPointer address) + { + Span buffer = new byte[Size]; + target.ReadBuffer(address, buffer); + FillFromBuffer(buffer); + } + public unsafe void FillFromBuffer(Span buffer) + { + Span structSpan = MemoryMarshal.CreateSpan(ref Context, 1); + Span byteSpan = MemoryMarshal.Cast(structSpan); + if (buffer.Length > sizeof(T)) + { + buffer.Slice(0, sizeof(T)).CopyTo(byteSpan); + } + else + { + buffer.CopyTo(byteSpan); + } + } + public unsafe byte[] GetBytes() + { + Span structSpan = MemoryMarshal.CreateSpan(ref Context, 1); + Span byteSpan = MemoryMarshal.AsBytes(structSpan); + return byteSpan.ToArray(); + } + public IPlatformAgnosticContext Clone() => new ContnextHolder() { Context = Context }; + public void Clear() => Context = default; + public void Unwind(Target target) => Context.Unwind(target); +} 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/IContext.cs new file mode 100644 index 00000000000000..7a8c3bf1ebacb3 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs @@ -0,0 +1,15 @@ +// 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.Contracts.StackWalkHelpers; + +public interface IPlatformContext +{ + public abstract uint Size { get; } + public abstract uint DefaultContextFlags { get; } + + 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/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs similarity index 68% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index 2fd6087e3bf8fc..de38e9babefebd 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Runtime.InteropServices; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -public interface IContext +public interface IPlatformAgnosticContext { public abstract uint Size { get; } public abstract uint DefaultContextFlags { get; } @@ -16,30 +15,23 @@ public interface IContext public TargetPointer FramePointer { get; set; } public abstract void Clear(); - public unsafe void ReadFromAddress(Target target, TargetPointer address) - { - Span buffer = new byte[Size]; - target.ReadBuffer(address, buffer); - FillFromBuffer(buffer); - } + public abstract void ReadFromAddress(Target target, TargetPointer address); public abstract void FillFromBuffer(Span buffer); public abstract byte[] GetBytes(); - public abstract IContext Clone(); + public abstract IPlatformAgnosticContext Clone(); public abstract void Unwind(Target target); - public static IContext GetContextForPlatform(Target target) + public static IPlatformAgnosticContext GetContextForPlatform(Target target) { switch (target.Platform) { case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: - AMD64Context amd64Context = default; - return amd64Context; + return new ContnextHolder(); case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: - ARM64Context arm64Context = default; - return arm64Context; + return new ContnextHolder(); default: throw new InvalidOperationException($"Unsupported platform {target.Platform}"); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/RegisterAttribute.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RegisterAttribute.cs similarity index 100% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/RegisterAttribute.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RegisterAttribute.cs diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs similarity index 100% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Unwinder.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs 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 index dcf0c0ff77a338..67154e3bd4521e 100644 --- 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 @@ -1,13 +1,7 @@ // 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; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -42,7 +36,7 @@ public bool Next() return true; } - public bool TryUpdateContext(ref IContext context) + public bool TryUpdateContext(ref IPlatformAgnosticContext context) { switch (CurrentFrame.Type) { 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 ad0308c77e5105..9873903f54fa3c 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 @@ -35,7 +35,7 @@ public enum StackWalkState internal struct StackDataFrameHandle : IStackDataFrameHandle { - internal IContext Context { get; init; } + internal IPlatformAgnosticContext Context { get; init; } internal StackWalkState State { get; init; } internal TargetPointer FrameAddress { get; init; } } @@ -43,10 +43,10 @@ internal struct StackDataFrameHandle : IStackDataFrameHandle internal class StackWalkHandle : IStackWalkHandle { public StackWalkState state; - public IContext context; + public IPlatformAgnosticContext context; public FrameIterator frameIter; - public StackWalkHandle(IContext context, FrameIterator frameIter, StackWalkState state) + public StackWalkHandle(IPlatformAgnosticContext context, FrameIterator frameIter, StackWalkState state) { this.context = context; this.frameIter = frameIter; @@ -56,7 +56,7 @@ public StackWalkHandle(IContext context, FrameIterator frameIter, StackWalkState IStackWalkHandle IStackWalk.CreateStackWalk(ThreadData threadData) { - IContext context = IContext.GetContextForPlatform(_target); + IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); FillContextFromThread(ref context, threadData); StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; return new StackWalkHandle(context, new(_target, threadData), state); @@ -147,7 +147,7 @@ private bool CheckForSkippedFrames(StackWalkHandle handle) } // get the caller context - IContext parentContext = handle.context.Clone(); + IPlatformAgnosticContext parentContext = handle.context.Clone(); parentContext.Unwind(_target); return handle.frameIter.CurrentFrameAddress.Value < parentContext.StackPointer.Value; @@ -202,7 +202,7 @@ private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle st return handle; } - private unsafe void FillContextFromThread(ref IContext refContext, ThreadData threadData) + private unsafe void FillContextFromThread(ref IPlatformAgnosticContext refContext, ThreadData threadData) { byte[] bytes = new byte[refContext.Size]; Span buffer = new Span(bytes); diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index 9979f205f68f3a..f0050454b47789 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -55,8 +55,8 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* int hrLocal = _legacyImpl.GetContext(contextFlags, contextBufSize, null, localContextBuf); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); - IContext contextStruct = IContext.GetContextForPlatform(_target); - IContext localContextStruct = IContext.GetContextForPlatform(_target); + IPlatformAgnosticContext contextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target); + IPlatformAgnosticContext localContextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target); contextStruct.FillFromBuffer(contextBuf); localContextStruct.FillFromBuffer(localContextBuf); From fb4109ca300b943ef7ee4ebbe71bd6f9bd89634a Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 10 Feb 2025 18:19:36 -0500 Subject: [PATCH 23/63] clean up --- .../debug/runtimeinfo/datadescriptor.h | 1 - .../DataType.cs | 31 ------- .../Target.cs | 3 + .../Contracts/StackWalk/Context/Unwinder.cs | 3 + .../Contracts/StackWalk/StackWalk_1.cs | 92 +++++++++---------- .../Data/Frames/Frame.cs | 31 ------- 6 files changed, 52 insertions(+), 109 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 71ca1b7bd3f21e..2a5fc807fe6758 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -527,7 +527,6 @@ CDAC_TYPE_FIELD(RealCodeHeader, /* T_RUNTIME_FUNCTION */, UnwindInfos, offsetof( #endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(RealCodeHeader) - CDAC_TYPE_BEGIN(CodeHeapListNode) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, Next, offsetof(HeapList, hpNext)) CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, StartAddress, offsetof(HeapList, startAddress)) 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 c97a76bdbbf778..bed6a8dcae1991 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -86,35 +86,4 @@ public enum DataType Frame, InlinedCallFrame, SoftwareExceptionFrame, - - ResumableFrame, - RedirectedTHreadFrame, - FaultingExceptionFrame, - FuncEvalFrame, - UnmanagedToManagedFrame, - ComMethodFrame, - CLRToCOMMethodFrame, - ComPrestubMethodFrame, - PInvokeCalliFrame, - HijackFrame, - PrestubMethodFrame, - CallCountingHelperFrame, - StubDispatchFrame, - ExternalMethodFrame, - DynamicHelperFrame, - InterpreterFrame, - ProtectByRefsFrame, - ProtectValueClassFrame, - DebuggerClassInitMarkFrame, - DebuggerSecurityCodeMarkFrame, - DebuggerExitFrame, - DebuggerU2MCatchHandlerFrame, - TailCallFrame, - ExceptionFilterFrame, - AssumeByrefFromJITStack, - HelperMethodFrame, - HelperMethodFrame_1OBJ, - HelperMethodFrame_2OBJ, - HelperMethodFrame_3OBJ, - HelperMethodFrame_PROTECTOBJ, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 86de2d13827d28..a05d7783fd459d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -18,6 +18,9 @@ namespace Microsoft.Diagnostics.DataContractReader; /// public abstract class Target { + /// + /// CorDebugPlatform represents the platform of the target. + /// public enum CorDebugPlatform : int { CORDB_PLATFORM_WINDOWS_X86 = 0, 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 52a8581b190a41..59f71507790baf 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 @@ -51,6 +51,9 @@ public static int AMD64Unwind( return ret; } + /// + /// Used to inject target into unwinder callbacks and track memory allocated for native unwinder. + /// private sealed class CallbackContext(Target target) : IDisposable { private bool disposed; 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 9873903f54fa3c..4a02024aa3cc4d 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 @@ -62,32 +62,6 @@ IStackWalkHandle IStackWalk.CreateStackWalk(ThreadData threadData) return new StackWalkHandle(context, new(_target, threadData), state); } - private void UpdateState(StackWalkHandle handle) - { - // If we are complete or in a bad state, no updating is required. - if (handle.state is StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE) - { - return; - } - - bool isManaged = IsManaged(handle.context.InstructionPointer, out _); - bool validFrame = handle.frameIter.IsValid(); - - if (isManaged) - { - handle.state = StackWalkState.SW_FRAMELESS; - if (CheckForSkippedFrames(handle)) - { - handle.state = StackWalkState.SW_SKIPPED_FRAME; - return; - } - } - else - { - handle.state = validFrame ? StackWalkState.SW_FRAME : StackWalkState.SW_COMPLETE; - } - } - bool IStackWalk.Next(IStackWalkHandle stackWalkHandle) { StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); @@ -124,15 +98,30 @@ bool IStackWalk.Next(IStackWalkHandle stackWalkHandle) return handle.state is not (StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE); } - IStackDataFrameHandle IStackWalk.GetCurrentFrame(IStackWalkHandle stackWalkHandle) + private void UpdateState(StackWalkHandle handle) { - StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); - return new StackDataFrameHandle + // If we are complete or in a bad state, no updating is required. + if (handle.state is StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE) { - Context = handle.context.Clone(), - State = handle.state, - FrameAddress = handle.frameIter.CurrentFrameAddress, - }; + return; + } + + bool isManaged = IsManaged(handle.context.InstructionPointer, out _); + bool validFrame = handle.frameIter.IsValid(); + + if (isManaged) + { + handle.state = StackWalkState.SW_FRAMELESS; + if (CheckForSkippedFrames(handle)) + { + handle.state = StackWalkState.SW_SKIPPED_FRAME; + return; + } + } + else + { + handle.state = validFrame ? StackWalkState.SW_FRAME : StackWalkState.SW_COMPLETE; + } } private bool CheckForSkippedFrames(StackWalkHandle handle) @@ -153,6 +142,17 @@ private bool CheckForSkippedFrames(StackWalkHandle handle) return handle.frameIter.CurrentFrameAddress.Value < parentContext.StackPointer.Value; } + IStackDataFrameHandle IStackWalk.GetCurrentFrame(IStackWalkHandle stackWalkHandle) + { + StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); + return new StackDataFrameHandle + { + Context = handle.context.Clone(), + State = handle.state, + FrameAddress = handle.frameIter.CurrentFrameAddress, + }; + } + byte[] IStackWalk.GetRawContext(IStackDataFrameHandle stackDataFrameHandle) { StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); @@ -182,6 +182,19 @@ private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle return false; } + private unsafe void FillContextFromThread(ref IPlatformAgnosticContext refContext, ThreadData threadData) + { + byte[] bytes = new byte[refContext.Size]; + Span buffer = new Span(bytes); + int hr = _target.GetThreadContext((uint)threadData.OSId.Value, refContext.DefaultContextFlags, refContext.Size, buffer); + if (hr != 0) + { + throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); + } + + refContext.FillFromBuffer(buffer); + } + private static StackWalkHandle AssertCorrectHandle(IStackWalkHandle stackWalkHandle) { if (stackWalkHandle is not StackWalkHandle handle) @@ -201,17 +214,4 @@ private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle st return handle; } - - private unsafe void FillContextFromThread(ref IPlatformAgnosticContext refContext, ThreadData threadData) - { - byte[] bytes = new byte[refContext.Size]; - Span buffer = new Span(bytes); - int hr = _target.GetThreadContext((uint)threadData.OSId.Value, refContext.DefaultContextFlags, refContext.Size, buffer); - if (hr != 0) - { - throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); - } - - refContext.FillFromBuffer(buffer); - } }; 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 f491cafc14875f..e25196be1d0cf3 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 @@ -11,37 +11,6 @@ internal sealed class Frame : IData private static readonly List SupportedFrameTypes = [ DataType.InlinedCallFrame, DataType.SoftwareExceptionFrame, - - DataType.ResumableFrame, - DataType.RedirectedTHreadFrame, - DataType.FaultingExceptionFrame, - DataType.FuncEvalFrame, - DataType.UnmanagedToManagedFrame, - DataType.ComMethodFrame, - DataType.CLRToCOMMethodFrame, - DataType.ComPrestubMethodFrame, - DataType.PInvokeCalliFrame, - DataType.HijackFrame, - DataType.PrestubMethodFrame, - DataType.CallCountingHelperFrame, - DataType.StubDispatchFrame, - DataType.ExternalMethodFrame, - DataType.DynamicHelperFrame, - DataType.InterpreterFrame, - DataType.ProtectByRefsFrame, - DataType.ProtectValueClassFrame, - DataType.DebuggerClassInitMarkFrame, - DataType.DebuggerSecurityCodeMarkFrame, - DataType.DebuggerExitFrame, - DataType.DebuggerU2MCatchHandlerFrame, - DataType.TailCallFrame, - DataType.ExceptionFilterFrame, - DataType.AssumeByrefFromJITStack, - DataType.HelperMethodFrame, - DataType.HelperMethodFrame_1OBJ, - DataType.HelperMethodFrame_2OBJ, - DataType.HelperMethodFrame_3OBJ, - DataType.HelperMethodFrame_PROTECTOBJ, ]; static Frame IData.Create(Target target, TargetPointer address) From 4fc3f226853b9811ed6b05aed6439724789fb78d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 11 Feb 2025 10:30:46 -0500 Subject: [PATCH 24/63] address easy to fix feedback --- .../Contracts/StackWalk/Context/ContextHolder.cs | 6 +++--- .../Contracts/StackWalk/Context/IPlatformAgnosticContext.cs | 4 ++-- src/native/managed/cdacreader/src/cdacreader.csproj | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) 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 40d702a9b91f6e..b5d194d0824fc2 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 @@ -6,7 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -public class ContnextHolder : IPlatformAgnosticContext where T : struct, IPlatformContext +public class CotnextHolder : IPlatformAgnosticContext where T : unmanaged, IPlatformContext { public T Context; @@ -25,7 +25,7 @@ public unsafe void ReadFromAddress(Target target, TargetPointer address) } public unsafe void FillFromBuffer(Span buffer) { - Span structSpan = MemoryMarshal.CreateSpan(ref Context, 1); + Span structSpan = new(ref Context); Span byteSpan = MemoryMarshal.Cast(structSpan); if (buffer.Length > sizeof(T)) { @@ -42,7 +42,7 @@ public unsafe byte[] GetBytes() Span byteSpan = MemoryMarshal.AsBytes(structSpan); return byteSpan.ToArray(); } - public IPlatformAgnosticContext Clone() => new ContnextHolder() { Context = Context }; + public IPlatformAgnosticContext Clone() => new CotnextHolder() { Context = Context }; public void Clear() => Context = default; public void Unwind(Target target) => Context.Unwind(target); } 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 de38e9babefebd..7cb0db6ea79a54 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 @@ -28,10 +28,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 ContnextHolder(); + return new CotnextHolder(); case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: - return new ContnextHolder(); + return new CotnextHolder(); default: throw new InvalidOperationException($"Unsupported platform {target.Platform}"); } diff --git a/src/native/managed/cdacreader/src/cdacreader.csproj b/src/native/managed/cdacreader/src/cdacreader.csproj index 692a79788e0dd5..f2e86840cd13fd 100644 --- a/src/native/managed/cdacreader/src/cdacreader.csproj +++ b/src/native/managed/cdacreader/src/cdacreader.csproj @@ -33,6 +33,7 @@ + From bfb63ee9b6c4f5f1e3dd1ec191684b57bc14062b Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 11 Feb 2025 11:40:53 -0500 Subject: [PATCH 25/63] move frame down casting logic to FrameIterator --- .../Contracts/StackWalk/FrameIterator.cs | 33 +++++++++++++++++-- .../Data/Frames/Frame.cs | 33 ++----------------- 2 files changed, 33 insertions(+), 33 deletions(-) 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 index 67154e3bd4521e..102b345f5d0a18 100644 --- 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 @@ -1,12 +1,19 @@ // 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; @@ -38,7 +45,7 @@ public bool Next() public bool TryUpdateContext(ref IPlatformAgnosticContext context) { - switch (CurrentFrame.Type) + switch (GetFrameType(CurrentFrame)) { case DataType.InlinedCallFrame: Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); @@ -58,11 +65,33 @@ public bool TryUpdateContext(ref IPlatformAgnosticContext context) public bool IsInlineCallFrameWithActiveCall() { - if (CurrentFrame.Type != DataType.InlinedCallFrame) + 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() + "VPtr"); + if (frame.VPtr == typeVptr) + { + return frameType; + } + } + catch (InvalidOperationException) + { + } + } + + return DataType.Unknown; + } } 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 e25196be1d0cf3..b6939f9d70b3bc 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 @@ -8,11 +8,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class Frame : IData { - private static readonly List SupportedFrameTypes = [ - DataType.InlinedCallFrame, - DataType.SoftwareExceptionFrame, - ]; - static Frame IData.Create(Target target, TargetPointer address) => new Frame(target, address); @@ -21,34 +16,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); - Type = FindType(target, address); - } - - private static DataType FindType(Target target, TargetPointer address) - { - TargetPointer instanceVptr = target.ReadPointer(address); - - 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() + "VPtr"); - if (instanceVptr == typeVptr) - { - return frameType; - } - } - catch (InvalidOperationException) - { - } - } - - return DataType.Unknown; + VPtr = target.ReadPointer(address); } public TargetPointer Address { get; init; } + public TargetPointer VPtr { get; init; } public TargetPointer Next { get; init; } - public DataType Type { get; init; } } From 1961e4d51607a16366de709686f646b4587f21bb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 11 Feb 2025 15:21:16 -0500 Subject: [PATCH 26/63] wip improve documentation --- docs/design/datacontracts/StackWalk.md | 116 +++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 66da186f5be5a4..bb55bbe5f40342 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -24,8 +24,124 @@ TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); ## Version 1 +This contract depends on the following descriptors: + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Frame` | `Next` | Pointer to next from on linked list | +| `InlinedCallFrame` | `CallSiteSP` | SP saved in Frame | +| `InlinedCallFrame` | `CallerReturnAddress` | Return address saved in Frame | +| `InlinedCallFrame` | `CalleeSavedFP` | FP saved in Frame | +| `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame | +| `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame | + + + The `StackWalk` contract provides an interface for walking the stack of a managed thread. It includes methods to create a stack walk, move to the next frame, get the current frame, retrieve the raw context, and get the frame address. +### Stackwalking +To create a full walk of the managed stack, two types of 'stacks' must be read. + +1. True call frames on the thread's stack +2. Capital "F" Frames (referred to as Frames as opposed to frames) which are used by the runtime for book keeping purposes. + +Capital "F" Frames are pushed and popped to a singly-linked list on the runtime's Thread object and are accessible using the [IThread](./Thread.md) contract. These capital "F" Frames are allocated within a functions call frame. For our purposes, these are relevant because they mark every transition where managed code calls native code. For more information about Frames see: [BOTR Stack Walking](https://github.com/dotnet/runtime/blob/44b7251f94772c69c2efb9daa7b69979d7ddd001/docs/design/coreclr/botr/stackwalking.md). + +Unwinding call frames on the stack usually requires an OS specific implementation. Unwind codes have different formats across Windows/MacOS/Linux. However, in our particular circumstance of unwinding only **managed function** call frames, the runtime uses Windows unwind logic/codes for all platforms. Therefore we delegate to the existing native unwinding code located in `src/coreclr/unwinder/`. For more information on Windows unwinding algorithm and codes see the following: + +* [Windows x64](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64) +* [Windows ARM64](https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling) + +#### Simplified Stackwalking Algorithm +The intuition for walking a managed stack is relatively simply. Capital "F" Frames are used as checkpoints to get into a section of managed code at which point we use the native unwinder until we get to a section of code that is not managed. Since captial "F" Frame mark every managed call into native code, by iterating the capital "F" Frames we ensure all managed call frames are walked. + +1. Read thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. +2. If the current IP is in managed code, use Windows style unwinding until the IP is not in managed code. +3. For each captial "F" Frame on the linked list: + 1. If the Frame can update the context, update the context. Frames where `NeedsUpdateRegDisplay() == TRUE` can update the context. + 2. If the context was updated and the new IP is in managed code, use Windows style unwinding until the IP is not in managed code. + +##### Example + +Given the following call stack and capital "F" Frames linked list, we can apply the above algorithm. + + + + + + + + + +
Call Stack (grow down) Capital "F" Frames Linked List
+ +``` + | Native | + - | | + |-----------| + | Managed | + |-----------| + | | + | Native | + | | + |-----------| + | | + | | + | Managed | + | | + | | + |-----------| + | | + | Native | + + | | + | StackBase | +``` + + +``` +InlinedCallFrame +(Context = ) + + || + \/ + +InlinedCallFrame +(Context = ) + + || + \/ + + NULL +``` + +
+ +* (1) We fetch the initial thread context ``, with SP pointing to the top of the call stack. +* (2) The context `` is not pointing to managed code, therefore we don't unwind. +This would be checked through the [ExecutionManager](./ExecutionManager.md) contract, but in our example we can see the SP corresponds to a native frame. +* (3.1) We look at the first Frame, an InlinedCallFrame with attatched context ``. Update our working context to ``. +* (3.2) Since we updated our context and our new context is in managed code, we use the Windows unwinding tool to iteratively unwind until the context is no longer managed. +This could take multible iterations, but would end up with context `` which has the first SP pointing to a native portion of the stack. +* (3.1) We look at the next Frame, an InlinedCallFrame with attatched context ``. Update our working context to ``. +* (3.2) Again, our updated context is in managed code. We will use the Windows unwinding tool to iteratively unwind until the context is no longer managed. This yields context ``. +* Since we are at a native context and have no more capital "F" Frames to process, the managed stack walk is complete. + +#### Stackwalking Algorithm +The actual implementation is a little more complex because: +* It requires pausing to return the context and Frame at certain points. +* Handles checking for "skipped frames". This can occur if an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call). + +1. Read thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. +2. If the current IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. +3. Iterate each Frame in the linked list: + 1. Check the current Frame, if it can update the context, do so. Frames where `Frame::NeedsUpdateRegDisplay() == TRUE` can update the context. + 2. For all Frame types except `InlinedCallFrame` with an active call, go to the next the Frame. + 3. Return the current context. + 4. Check for skipped frames by comparing the address of the current Frame (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding context one iteration). + If the address of the Frame is less than the caller's stack pointer, go to the next Frame, return the current context and repeat this step. + 5. If the context was updated and the new IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. + + ### CreateStackWalk ```csharp From ce13fdc1fdb02f617a9096ab24a51c6708ac1c07 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 11 Feb 2025 15:21:53 -0500 Subject: [PATCH 27/63] typo --- docs/design/datacontracts/StackWalk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index bb55bbe5f40342..97b475509d2c50 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -128,7 +128,7 @@ This could take multible iterations, but would end up with context `` which h #### Stackwalking Algorithm The actual implementation is a little more complex because: -* It requires pausing to return the context and Frame at certain points. +* It requires pausing to return the current context and Frame at certain points. * Handles checking for "skipped frames". This can occur if an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call). 1. Read thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. From 4e40652c4848184abfbeb975b703828636051eeb Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 12 Feb 2025 10:58:54 -0500 Subject: [PATCH 28/63] use FrameIdentifier instead of VPtr --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 4 ++-- .../Contracts/StackWalk/FrameIterator.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 2a5fc807fe6758..859428044fd8a9 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -602,9 +602,9 @@ CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) -// Add VPtr for all defined Frame types. Used to differentiate Frame objects. +// Add FrameIdentifier for all defined Frame types. Used to differentiate Frame objects. #define FRAME_TYPE_NAME(frameType) \ - CDAC_GLOBAL_POINTER(frameType##VPtr, frameType::GetMethodFrameVPtr()) + CDAC_GLOBAL_POINTER(frameType##Identifier, FrameIdentifier::frameType) #include "frames.h" #undef FRAME_TYPE_NAME 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 index 102b345f5d0a18..4f25c1509269b8 100644 --- 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 @@ -81,7 +81,7 @@ private DataType GetFrameType(Data.Frame frame) try { // not all Frames are in all builds, so we need to catch the exception - typeVptr = target.ReadGlobalPointer(frameType.ToString() + "VPtr"); + typeVptr = target.ReadGlobalPointer(frameType.ToString() + "Identifier"); if (frame.VPtr == typeVptr) { return frameType; From af93b5d10b4347998af8bc6c66bb593b46b9ecc7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 12 Feb 2025 10:59:09 -0500 Subject: [PATCH 29/63] docs wip --- docs/design/datacontracts/StackWalk.md | 104 ++++++++++++------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 97b475509d2c50..7ce480a21af794 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -1,6 +1,6 @@ # Contract StackWalk -This contract encapsulates support for StackWalking managed threads. +This contract encapsulates support for walking the stack of managed threads. ## APIs of contract @@ -23,6 +23,17 @@ TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); ``` ## Version 1 +To create a full walk of the managed stack, two types of 'stacks' must be read. + +1. True call frames on the thread's stack +2. Capital "F" Frames (referred to as Frames as opposed to frames) which are used by the runtime for book keeping purposes. + +Capital "F" Frames are pushed and popped to a singly-linked list on the runtime's Thread object and are accessible using the [IThread](./Thread.md) contract. These capital "F" Frames are allocated within a functions call frame, meaning they also live on the stack. A subset of Frame types store extra data allowing us to recover a portion of the context from when they were created For our purposes, these are relevant because they mark every transition where managed code calls native code. For more information about Frames see: [BOTR Stack Walking](https://github.com/dotnet/runtime/blob/44b7251f94772c69c2efb9daa7b69979d7ddd001/docs/design/coreclr/botr/stackwalking.md). + +Unwinding call frames on the stack usually requires an OS specific implementation. However, in our particular circumstance of unwinding only **managed function** call frames, the runtime uses Windows unwind logic/codes for all platforms (this isn't true for NativeAOT). Therefore we can delegate to the existing native unwinding code located in `src/coreclr/unwinder/`. For more information on the Windows unwinding algorithm and unwind codes see the following docs: + +* [Windows x64](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64) +* [Windows ARM64](https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling) This contract depends on the following descriptors: @@ -35,59 +46,54 @@ This contract depends on the following descriptors: | `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame | | `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame | - - -The `StackWalk` contract provides an interface for walking the stack of a managed thread. It includes methods to create a stack walk, move to the next frame, get the current frame, retrieve the raw context, and get the frame address. - -### Stackwalking -To create a full walk of the managed stack, two types of 'stacks' must be read. - -1. True call frames on the thread's stack -2. Capital "F" Frames (referred to as Frames as opposed to frames) which are used by the runtime for book keeping purposes. - -Capital "F" Frames are pushed and popped to a singly-linked list on the runtime's Thread object and are accessible using the [IThread](./Thread.md) contract. These capital "F" Frames are allocated within a functions call frame. For our purposes, these are relevant because they mark every transition where managed code calls native code. For more information about Frames see: [BOTR Stack Walking](https://github.com/dotnet/runtime/blob/44b7251f94772c69c2efb9daa7b69979d7ddd001/docs/design/coreclr/botr/stackwalking.md). +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| For each FrameType ``, `##Identifier` | FrameIdentifier enum value | Identifier used to determine concrete type of Frames | -Unwinding call frames on the stack usually requires an OS specific implementation. Unwind codes have different formats across Windows/MacOS/Linux. However, in our particular circumstance of unwinding only **managed function** call frames, the runtime uses Windows unwind logic/codes for all platforms. Therefore we delegate to the existing native unwinding code located in `src/coreclr/unwinder/`. For more information on Windows unwinding algorithm and codes see the following: +Contracts used: +| Contract Name | +| --- | +| `ExecutionManager` | +| `Thread` | -* [Windows x64](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64) -* [Windows ARM64](https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling) -#### Simplified Stackwalking Algorithm -The intuition for walking a managed stack is relatively simply. Capital "F" Frames are used as checkpoints to get into a section of managed code at which point we use the native unwinder until we get to a section of code that is not managed. Since captial "F" Frame mark every managed call into native code, by iterating the capital "F" Frames we ensure all managed call frames are walked. +### Simplified Stackwalking Algorithm +The intuition for walking a managed stack is relatively simply: unwind managed portions of the stack until we hit native code then use capital "F" Frames as checkpoints to get into new sections of managed code. Because Frames are added at each point before managed code (higher SP value) calls native code (lower SP values), we are guarenteed that a Frame exists at the top (lower SP value) of each managed call frame run. -1. Read thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. +1. Read the thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. 2. If the current IP is in managed code, use Windows style unwinding until the IP is not in managed code. 3. For each captial "F" Frame on the linked list: - 1. If the Frame can update the context, update the context. Frames where `NeedsUpdateRegDisplay() == TRUE` can update the context. + 1. If the Frame can update the context, update the context. 2. If the context was updated and the new IP is in managed code, use Windows style unwinding until the IP is not in managed code. -##### Example +#### Example Given the following call stack and capital "F" Frames linked list, we can apply the above algorithm. - +
Call Stack (grow down) Call Stack (growing down) Capital "F" Frames Linked List
``` - | Native | + | Native | <- 's SP - | | - |-----------| + |-----------| <- 's SP | Managed | - |-----------| + |-----------| <- 's SP | | | Native | | | - |-----------| + |-----------| <- 's SP | | | | | Managed | | | | | - |-----------| + |-----------| <- 's SP | | | Native | + | | @@ -117,8 +123,8 @@ InlinedCallFrame
* (1) We fetch the initial thread context `
`, with SP pointing to the top of the call stack. -* (2) The context `` is not pointing to managed code, therefore we don't unwind. -This would be checked through the [ExecutionManager](./ExecutionManager.md) contract, but in our example we can see the SP corresponds to a native frame. +* (2) In our example we can see that ``'s SP is pointing to a native frame on the stack. Therefore context `` is not pointing to managed code and we don't unwind. +In actuality, this is checked through the [ExecutionManager](./ExecutionManager.md) contract. * (3.1) We look at the first Frame, an InlinedCallFrame with attatched context ``. Update our working context to ``. * (3.2) Since we updated our context and our new context is in managed code, we use the Windows unwinding tool to iteratively unwind until the context is no longer managed. This could take multible iterations, but would end up with context `` which has the first SP pointing to a native portion of the stack. @@ -126,58 +132,50 @@ This could take multible iterations, but would end up with context `` which h * (3.2) Again, our updated context is in managed code. We will use the Windows unwinding tool to iteratively unwind until the context is no longer managed. This yields context ``. * Since we are at a native context and have no more capital "F" Frames to process, the managed stack walk is complete. -#### Stackwalking Algorithm -The actual implementation is a little more complex because: +### Stackwalking Algorithm +In reality, the actual algorithm is a little more complex because: * It requires pausing to return the current context and Frame at certain points. -* Handles checking for "skipped frames". This can occur if an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call). +* Handles checking for "skipped frames" which can occur if an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call). -1. Read thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. +1. Read the thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. 2. If the current IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. 3. Iterate each Frame in the linked list: - 1. Check the current Frame, if it can update the context, do so. Frames where `Frame::NeedsUpdateRegDisplay() == TRUE` can update the context. - 2. For all Frame types except `InlinedCallFrame` with an active call, go to the next the Frame. + 1. Check the current Frame, if it can update the context, do so. + 2. For all Frame except `InlinedCallFrame` with an active call, go to the next the Frame. 3. Return the current context. - 4. Check for skipped frames by comparing the address of the current Frame (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding context one iteration). + 4. Check for skipped frames by comparing the address of the current Frame (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding current context one iteration). If the address of the Frame is less than the caller's stack pointer, go to the next Frame, return the current context and repeat this step. 5. If the context was updated and the new IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. +### APIs -### CreateStackWalk - -```csharp -IStackWalkHandle CreateStackWalk(ThreadData threadData); -``` - -Creates a stack walk handle for the given thread data. This initializes the context and frame iterator for the stack walk. -### Next +The majority of the contract's complexity comes from the stack walking algorithm implementation which is implemented and controlled through the following APIs. +These handle setting up and iterating the stackwalk state according to the algorithm detailed above. ```csharp +IStackWalkHandle CreateStackWalk(ThreadData threadData); bool Next(IStackWalkHandle stackWalkHandle); ``` -Moves to the next frame in the stack walk. Returns `true` if successfully moved to the next frame. Otherwise returns `false`. - -### GetCurrentFrame +The rest of the APIs convey state about the stack walk at a given point which fall out of the stack walking algorithm relatively simply. +`GetCurrentFrame` creates a copy of the stack walk state which remains constant even if the stack walk is iterated. ```csharp IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); ``` -Gets the current frame in the stack walk. Returns a handle to the stack data frame. - -### GetRawContext - +`GetRawContext` Retrieves the raw Windows thread context of the current frame as a byte array. The size and shape of the context is platform dependent. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. +This context is not guarenteed to be complete. Not all capital "F" Frames store the entire context, some only store the IP/SP/FP. Therefore, at points where the context is based on these Frames it will be incomplete. ```csharp byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); ``` -Retrieves the raw Windows thread context of the current frame as a byte array. The size and shape of the context is platform dependent. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. -### GetFrameAddress +`GetFrameAddress` gets the address of the current capital "F" Frame. This is only valid if the `IStackDataFrameHandle` is at a point where the context is based on a capital "F" Frame. For example, it is not valid when when the current context was created by using the Windows unwinder. +If the Frame is not valid, returns `TargetPointer.Null`. ```csharp TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle); ``` -Gets the frame address of the current frame. Returns `TargetPointer.Null` if the frame is not valid. From 0d2c3d908a378a5cefeff6890844e6de7ba23ed6 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Wed, 12 Feb 2025 14:50:25 -0500 Subject: [PATCH 30/63] improve docs --- docs/design/datacontracts/StackWalk.md | 78 +++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 7ce480a21af794..5f661853ab2560 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -58,16 +58,25 @@ Contracts used: | `Thread` | -### Simplified Stackwalking Algorithm -The intuition for walking a managed stack is relatively simply: unwind managed portions of the stack until we hit native code then use capital "F" Frames as checkpoints to get into new sections of managed code. Because Frames are added at each point before managed code (higher SP value) calls native code (lower SP values), we are guarenteed that a Frame exists at the top (lower SP value) of each managed call frame run. +### Stackwalk Algorithm +The intuition for walking a managed stack is relatively simply: unwind managed portions of the stack until we hit native code then use capital "F" Frames as checkpoints to get into new sections of managed code. Because Frames are added at each point before managed code (higher SP value) calls native code (lower SP values), we are guaranteed that a Frame exists at the top (lower SP value) of each managed call frame run. + +In reality, the actual algorithm is a little more complex fow two reasons. It requires pausing to return the current context and Frame at certain points and it checks for "skipped Frames" which can occur if an capital "F" Frame is allocated in a managed stack frame (e.g. an inlined P/Invoke call). + 1. Read the thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. -2. If the current IP is in managed code, use Windows style unwinding until the IP is not in managed code. -3. For each captial "F" Frame on the linked list: - 1. If the Frame can update the context, update the context. - 2. If the context was updated and the new IP is in managed code, use Windows style unwinding until the IP is not in managed code. +2. If the current IP is in managed code, use Windows style unwinding iteratively, returning the updated context at each iteration, until the IP is not in managed code. +3. Iterate each Frame in the linked list: + 1. Check the current Frame, if it can update the context, do so. + 2. For all Frame except `InlinedCallFrame` with an active call, go to the next the Frame. + 3. Return the current context. + 4. Check for skipped Frames by comparing the address of the current Frame (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding current context one iteration). + If the address of the Frame is less than the caller's stack pointer, go to the next Frame, return the current context and repeat this step. + 5. If the context was updated and the new IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. + +#### Simple Example -#### Example +In this example we walk through the algorithm without instances of skipped Frames. Given the following call stack and capital "F" Frames linked list, we can apply the above algorithm. @@ -103,19 +112,19 @@ Given the following call stack and capital "F" Frames linked list, we can apply @@ -125,31 +134,22 @@ InlinedCallFrame * (1) We fetch the initial thread context ``, with SP pointing to the top of the call stack. * (2) In our example we can see that ``'s SP is pointing to a native frame on the stack. Therefore context `` is not pointing to managed code and we don't unwind. In actuality, this is checked through the [ExecutionManager](./ExecutionManager.md) contract. -* (3.1) We look at the first Frame, an InlinedCallFrame with attatched context ``. Update our working context to ``. -* (3.2) Since we updated our context and our new context is in managed code, we use the Windows unwinding tool to iteratively unwind until the context is no longer managed. -This could take multible iterations, but would end up with context `` which has the first SP pointing to a native portion of the stack. -* (3.1) We look at the next Frame, an InlinedCallFrame with attatched context ``. Update our working context to ``. -* (3.2) Again, our updated context is in managed code. We will use the Windows unwinding tool to iteratively unwind until the context is no longer managed. This yields context ``. -* Since we are at a native context and have no more capital "F" Frames to process, the managed stack walk is complete. - -### Stackwalking Algorithm -In reality, the actual algorithm is a little more complex because: -* It requires pausing to return the current context and Frame at certain points. -* Handles checking for "skipped frames" which can occur if an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call). - -1. Read the thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. -2. If the current IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. -3. Iterate each Frame in the linked list: - 1. Check the current Frame, if it can update the context, do so. - 2. For all Frame except `InlinedCallFrame` with an active call, go to the next the Frame. - 3. Return the current context. - 4. Check for skipped frames by comparing the address of the current Frame (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding current context one iteration). - If the address of the Frame is less than the caller's stack pointer, go to the next Frame, return the current context and repeat this step. - 5. If the context was updated and the new IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. +* (3.1) We look at the current (first) Frame, a SoftwareExceptionFrame with attached context ``. Update our working context to ``. +* (3.2) Since the current Frame is not an InlinedCallFrame, set the current Frame to the next Frame on the chain. +* (3.3) Return the current context and Frame. +* (3.4) The example has no skipped Frames so we skip this step. +* (3.5) Since we updated our context and our new context is in managed code, we use the Windows style unwinding tool to iteratively unwind until the context is no longer managed. +This could take multiple iterations, each time returning a new context to the caller. We end up with context `` which is the first context inside of a native call frame. +* (3.1) We look at the current Frame, another SoftwareExceptionFrame with attached context ``. Update our working context to ``. +* (3.2) Since the current Frame is not an InlinedCallFrame, set the current Frame to the next Frame (`NULL TERMINATOR`) on the chain. +* (3.3) Return the current context and Frame. +* (3.4) The example has no skipped Frames so we skip this step. +* (3.5) Again, we updated our context and our new context is in managed code. Therefore, we use the Windows style unwinding tool to iteratively unwind until the context is no longer managed. +This could take multiple iterations, each time returning a new context to the caller. We end up with context `` which is the first context inside of a native call frame. +* (3) Since our current context is in native code and the current Frame is the `NULL TERMINATOR`, we are done. ### APIs - The majority of the contract's complexity comes from the stack walking algorithm implementation which is implemented and controlled through the following APIs. These handle setting up and iterating the stackwalk state according to the algorithm detailed above. @@ -166,7 +166,7 @@ IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); ``` `GetRawContext` Retrieves the raw Windows thread context of the current frame as a byte array. The size and shape of the context is platform dependent. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. -This context is not guarenteed to be complete. Not all capital "F" Frames store the entire context, some only store the IP/SP/FP. Therefore, at points where the context is based on these Frames it will be incomplete. +This context is not guaranteed to be complete. Not all capital "F" Frames store the entire context, some only store the IP/SP/FP. Therefore, at points where the context is based on these Frames it will be incomplete. ```csharp byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); ``` From 82e9ece9608ece2ebec752b0c3cb9ddc597d4751 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 11:56:46 -0500 Subject: [PATCH 31/63] be more consistent when referring to Windows "style" unwinding/context --- docs/design/datacontracts/StackWalk.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 5f661853ab2560..c35c5273ca5eec 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -30,7 +30,7 @@ To create a full walk of the managed stack, two types of 'stacks' must be read. Capital "F" Frames are pushed and popped to a singly-linked list on the runtime's Thread object and are accessible using the [IThread](./Thread.md) contract. These capital "F" Frames are allocated within a functions call frame, meaning they also live on the stack. A subset of Frame types store extra data allowing us to recover a portion of the context from when they were created For our purposes, these are relevant because they mark every transition where managed code calls native code. For more information about Frames see: [BOTR Stack Walking](https://github.com/dotnet/runtime/blob/44b7251f94772c69c2efb9daa7b69979d7ddd001/docs/design/coreclr/botr/stackwalking.md). -Unwinding call frames on the stack usually requires an OS specific implementation. However, in our particular circumstance of unwinding only **managed function** call frames, the runtime uses Windows unwind logic/codes for all platforms (this isn't true for NativeAOT). Therefore we can delegate to the existing native unwinding code located in `src/coreclr/unwinder/`. For more information on the Windows unwinding algorithm and unwind codes see the following docs: +Unwinding call frames on the stack usually requires an OS specific implementation. However, in our particular circumstance of unwinding only **managed function** call frames, the runtime uses Windows style unwind logic/codes for all platforms (this isn't true for NativeAOT). Therefore we can delegate to the existing native unwinding code located in `src/coreclr/unwinder/`. For more information on the Windows unwinding algorithm and unwind codes see the following docs: * [Windows x64](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64) * [Windows ARM64](https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling) @@ -165,14 +165,18 @@ The rest of the APIs convey state about the stack walk at a given point which fa IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); ``` -`GetRawContext` Retrieves the raw Windows thread context of the current frame as a byte array. The size and shape of the context is platform dependent. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. +`GetRawContext` Retrieves the raw Windows style thread context of the current frame as a byte array. The size and shape of the context is platform dependent. + +* On Windows the context is defined directly in Windows header `winnt.h`. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. +* On non-Windows platform the context's are defined in `src/coreclr/pal/inc/pal.h` and should mimic the Windows structure. + This context is not guaranteed to be complete. Not all capital "F" Frames store the entire context, some only store the IP/SP/FP. Therefore, at points where the context is based on these Frames it will be incomplete. ```csharp byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); ``` -`GetFrameAddress` gets the address of the current capital "F" Frame. This is only valid if the `IStackDataFrameHandle` is at a point where the context is based on a capital "F" Frame. For example, it is not valid when when the current context was created by using the Windows unwinder. +`GetFrameAddress` gets the address of the current capital "F" Frame. This is only valid if the `IStackDataFrameHandle` is at a point where the context is based on a capital "F" Frame. For example, it is not valid when when the current context was created by using the stack frame unwinder. If the Frame is not valid, returns `TargetPointer.Null`. ```csharp From 333a93295363ceba1f82865dbb33fbddd56a7f1b Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 12:46:21 -0500 Subject: [PATCH 32/63] refactor unwinder CMakeLists.txt --- src/coreclr/unwinder/CMakeLists.txt | 43 +++++++++++------------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/coreclr/unwinder/CMakeLists.txt b/src/coreclr/unwinder/CMakeLists.txt index 05785877d3fb3c..8d8ddc7c154edb 100644 --- a/src/coreclr/unwinder/CMakeLists.txt +++ b/src/coreclr/unwinder/CMakeLists.txt @@ -1,3 +1,16 @@ +# helper to add set of include directories to unwinder targets +macro(add_unwinder_include_directories TARGET) + target_include_directories(${TARGET} BEFORE PRIVATE ${VM_DIR}) + target_include_directories(${TARGET} BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) + target_include_directories(${TARGET} BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_include_directories(${TARGET} BEFORE PRIVATE ${CLR_DIR}/unwinder) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/debug/ee) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/gc) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/gcdump) + target_include_directories(${TARGET} PRIVATE ${CLR_DIR}/debug/daccess) + target_include_directories(${TARGET} PRIVATE ${ARCH_SOURCES_DIR}) +endmacro() + set(UNWINDER_SOURCES baseunwinder.cpp ${ARCH_SOURCES_DIR}/unwinder.cpp @@ -7,28 +20,12 @@ convert_to_absolute_path(UNWINDER_SOURCES ${UNWINDER_SOURCES}) if(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_wks OBJECT ${UNWINDER_SOURCES}) - target_include_directories(unwinder_wks BEFORE PRIVATE ${VM_DIR}) - target_include_directories(unwinder_wks BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) - target_include_directories(unwinder_wks BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_include_directories(unwinder_wks BEFORE PRIVATE ${CLR_DIR}/unwinder) - target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/debug/ee) - target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/gc) - target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/gcdump) - target_include_directories(unwinder_wks PRIVATE ${CLR_DIR}/debug/daccess) - target_include_directories(unwinder_wks PRIVATE ${ARCH_SOURCES_DIR}) + add_unwinder_include_directories(unwinder_wks) add_dependencies(unwinder_wks eventing_headers) endif(CLR_CMAKE_HOST_UNIX) add_library_clr(unwinder_dac ${UNWINDER_SOURCES}) -target_include_directories(unwinder_dac BEFORE PRIVATE ${VM_DIR}) -target_include_directories(unwinder_dac BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) -target_include_directories(unwinder_dac BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(unwinder_dac BEFORE PRIVATE ${CLR_DIR}/unwinder) -target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/debug/ee) -target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/gc) -target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/gcdump) -target_include_directories(unwinder_dac PRIVATE ${CLR_DIR}/debug/daccess) -target_include_directories(unwinder_dac PRIVATE ${ARCH_SOURCES_DIR}) +add_unwinder_include_directories(unwinder_dac) add_dependencies(unwinder_dac eventing_headers) set_target_properties(unwinder_dac PROPERTIES DAC_COMPONENT TRUE) target_compile_definitions(unwinder_dac PRIVATE FEATURE_NO_HOST) @@ -63,15 +60,7 @@ function(create_platform_unwinder) ${UNWINDER_SOURCES} ) - target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${VM_DIR}) - target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${VM_DIR}/${ARCH_SOURCES_DIR}) - target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_include_directories(${TARGETDETAILS_TARGET} BEFORE PRIVATE ${CLR_DIR}/unwinder) - target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/debug/ee) - target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/gc) - target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/gcdump) - target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${CLR_DIR}/debug/daccess) - target_include_directories(${TARGETDETAILS_TARGET} PRIVATE ${ARCH_SOURCES_DIR}) + add_unwinder_include_directories(${TARGETDETAILS_TARGET}) target_link_libraries(${TARGETDETAILS_TARGET} PRIVATE ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB}) From bcdf2e4da06b3bd996949d60a81e3f5156066a99 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 12:50:47 -0500 Subject: [PATCH 33/63] move UNWINDER_ASSERT define into base unwinder --- src/coreclr/unwinder/amd64/unwinder.cpp | 9 --------- src/coreclr/unwinder/arm64/unwinder.cpp | 6 ------ src/coreclr/unwinder/baseunwinder.h | 11 +++++++++++ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index c85ded861f5b6b..bb057cea521934 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -56,9 +56,6 @@ M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) #ifdef DACCESS_COMPILE -// Report failure in the unwinder if the condition is FALSE -#define UNWINDER_ASSERT(Condition) if (!(Condition)) DacError(CORDBG_E_TARGET_INCONSISTENT) - //--------------------------------------------------------------------------------------- // // The InstructionBuffer class abstracts accessing assembler instructions in the function @@ -262,9 +259,6 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) #elif defined(FEATURE_CDAC_UNWINDER) -// TODO: add asserts on cDAC build without importing more headers. -#define UNWINDER_ASSERT(x) - // Read 64 bit unsigned value from the specified addres when the unwinder is build // for the cDAC. This triggers a callback to the cDAC host to read the memory from // the target process. @@ -451,9 +445,6 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) #else // !DACCESS_COMPILE && !FEATURE_CDAC_UNWINDER -// Report failure in the unwinder if the condition is FALSE -#define UNWINDER_ASSERT _ASSERTE - // For unwinding of the jitted code on non-Windows platforms, the Instruction buffer is // just a plain pointer to the instruction data. typedef UCHAR * InstructionBuffer; diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index 9b81d20cae5d0f..5cc708e9b02676 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -24,12 +24,6 @@ #define PRUNTIME_FUNCTION PT_RUNTIME_FUNCTION #endif -#ifndef FEATURE_CDAC_UNWINDER -#define UNWINDER_ASSERT _ASSERTE -#else // !FEATURE_CDAC_UNWINDER -#define UNWINDER_ASSERT(x) -#endif // FEATURE_CDAC_UNWINDER - #ifndef __in #define __in _In_ #define __out _Out_ diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 3e809e0aa759ce..6e32861f959f68 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -12,6 +12,17 @@ using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer, void* callba using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry, void* callbackContext); #endif // FEATURE_CDAC_UNWINDER + +// Report failure in the unwinder if the condition is FALSE +#if defined(FEATURE_CDAC_UNWINDER) +// TODO: Add cDAC UNWINDER_ASSERT +#define UNWINDER_ASSERT(x) +#elif defined(DACCESS_COMPILE) +#define UNWINDER_ASSERT(Condition) if (!(Condition)) DacError(CORDBG_E_TARGET_INCONSISTENT) +#else // !DACCESS_COMPILE AND !FEATURE_CDAC_UNWINDER +#define UNWINDER_ASSERT _ASSERTE +#endif + //--------------------------------------------------------------------------------------- // // OOPStackUnwinder is the abstract base class for unwinding stack frames. Each of the two 64-bit platforms From d3fdf0b8384d8b17bf487b54fafad639bc1653fc Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 13:07:39 -0500 Subject: [PATCH 34/63] refactor unwinder to use same InstructionBuffer for DAC/cDAC --- src/coreclr/unwinder/amd64/unwinder.cpp | 159 ++++++++---------------- 1 file changed, 50 insertions(+), 109 deletions(-) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index bb057cea521934..95d35d245ca469 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -52,10 +52,29 @@ M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } -#endif // FEATURE_CDAC_UNWINDER +#else +// Read 64 bit unsigned value from the specified addres when the unwinder is build +// for the cDAC. This triggers a callback to the cDAC host to read the memory from +// the target process. +ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) +{ + ULONG64 value; + readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); + return value; +} -#ifdef DACCESS_COMPILE +// Read 128 bit value from the specified addres when the unwinder is build +// for the cDAC. This triggers a callback to the cDAC host to read the memory from +// the target process. +M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) +{ + M128A value; + readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); + return value; +} +#endif // FEATURE_CDAC_UNWINDER +#if defined(DACCESS_COMPILE) || defined(FEATURE_CDAC_UNWINDER) //--------------------------------------------------------------------------------------- // // The InstructionBuffer class abstracts accessing assembler instructions in the function @@ -69,6 +88,24 @@ class InstructionBuffer SIZE_T m_address; UCHAR m_buffer[32]; +#ifdef FEATURE_CDAC_UNWINDER + ReadFromTarget m_readFromTarget; + void* m_callbackContext; + + // Load the instructions from the target process being debugged + HRESULT Load() + { + HRESULT hr = m_readFromTarget(m_address, m_buffer, sizeof(m_buffer), m_callbackContext); + if (SUCCEEDED(hr)) + { + // TODO: Implement breakpoint patching for cDAC + // https://github.com/dotnet/runtime/issues/112273#issue-2838620747 + } + + return hr; + } +#else // FEATURE_CDAC_UNWINDER + // Load the instructions from the target process being debugged HRESULT Load() { @@ -84,13 +121,22 @@ class InstructionBuffer return hr; } +#endif public: // Construct the InstructionBuffer for the given address in the target process +#ifdef FEATURE_CDAC_UNWINDER + InstructionBuffer(SIZE_T address, ReadFromTarget readFromTarget, void* callbackContext) + : m_offset(0), + m_address(address), + m_readFromTarget(readFromTarget), + m_callbackContext(callbackContext) +#else // FEATURE_CDAC_UNWINDER InstructionBuffer(SIZE_T address) : m_offset(0), m_address(address) +#endif // FEATURE_CDAC_UNWINDER { HRESULT hr = Load(); if (FAILED(hr)) @@ -136,7 +182,9 @@ class InstructionBuffer return m_buffer[realIndex]; } }; +#endif // DACCESS_COMPILE || FEATURE_CDAC_UNWINDER +#ifdef DACCESS_COMPILE //--------------------------------------------------------------------------------------- // // Given the target address of an UNWIND_INFO structure, this function retrieves all the memory used for @@ -259,113 +307,6 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) #elif defined(FEATURE_CDAC_UNWINDER) -// Read 64 bit unsigned value from the specified addres when the unwinder is build -// for the cDAC. This triggers a callback to the cDAC host to read the memory from -// the target process. -ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) -{ - ULONG64 value; - readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); - return value; -} - -// Read 128 bit value from the specified addres when the unwinder is build -// for the cDAC. This triggers a callback to the cDAC host to read the memory from -// the target process. -M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) -{ - M128A value; - readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); - return value; -} - -//--------------------------------------------------------------------------------------- -// -// The InstructionBuffer class abstracts accessing assembler instructions in the function -// being unwound. It behaves as a memory byte pointer, but it reads the instruction codes -// from the target process being debugged and removes all changes that the debugger -// may have made to the code, e.g. breakpoint instructions. -// -class InstructionBuffer -{ - UINT m_offset; - SIZE_T m_address; - UCHAR m_buffer[32]; - - ReadFromTarget readFromTarget; - void* callbackContext; - - // Load the instructions from the target process being debugged - HRESULT Load() - { - HRESULT hr = readFromTarget(m_address, m_buffer, sizeof(m_buffer), callbackContext); - if (SUCCEEDED(hr)) - { - // TODO: Implement breakpoint patching for cDAC - // https://github.com/dotnet/runtime/issues/112273#issue-2838620747 - - // On X64, we need to replace any patches which are within the requested memory range. - // This is because the X64 unwinder needs to disassemble the native instructions in order to determine - // whether the IP is in an epilog. - } - - return hr; - } - -public: - - // Construct the InstructionBuffer for the given address in the target process - InstructionBuffer(SIZE_T address, ReadFromTarget readFromTarget, void* callbackContext) - : m_offset(0), - m_address(address), - readFromTarget(readFromTarget), - callbackContext(callbackContext) - { - HRESULT hr = Load(); - if (FAILED(hr)) - { - // If we have failed to read from the target process, just pretend - // we've read zeros. - // The InstructionBuffer is used in code driven epilogue unwinding - // when we read processor instructions and simulate them. - // It's very rare to be stopped in an epilogue when - // getting a stack trace, so if we can't read the - // code just assume we aren't in an epilogue instead of failing - // the unwind. - memset(m_buffer, 0, sizeof(m_buffer)); - } - } - - // Move to the next byte in the buffer - InstructionBuffer& operator++() - { - m_offset++; - return *this; - } - - // Skip delta bytes in the buffer - InstructionBuffer& operator+=(INT delta) - { - m_offset += delta; - return *this; - } - - // Return address of the current byte in the buffer - explicit operator ULONG64() - { - return m_address + m_offset; - } - - // Get the byte at the given index from the current position - // Invoke DacError if the index is out of the buffer - UCHAR operator[](int index) - { - int realIndex = m_offset + index; - UNWINDER_ASSERT(realIndex < (int)sizeof(m_buffer)); - return m_buffer[realIndex]; - } -}; - BOOL amd64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) { HRESULT hr = E_FAIL; From a2467753bc581c680cb97ca4820f8ed1c587bed7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 13:31:58 -0500 Subject: [PATCH 35/63] prefix class members with m_ --- src/coreclr/unwinder/amd64/unwinder.cpp | 12 ++++++------ src/coreclr/unwinder/arm64/unwinder.cpp | 2 +- src/coreclr/unwinder/baseunwinder.cpp | 6 +++--- src/coreclr/unwinder/baseunwinder.h | 16 ++++++++-------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 95d35d245ca469..95cf51bb7e6291 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -59,7 +59,7 @@ M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) { ULONG64 value; - readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); + m_readFromTarget((uint64_t)addr, &value, sizeof(value), m_callbackContext); return value; } @@ -69,7 +69,7 @@ ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) { M128A value; - readFromTarget((uint64_t)addr, &value, sizeof(value), callbackContext); + m_readFromTarget((uint64_t)addr, &value, sizeof(value), m_callbackContext); return value; } #endif // FEATURE_CDAC_UNWINDER @@ -349,7 +349,7 @@ BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) { UNWIND_INFO unwindInfo; - if(readFromTarget((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo), callbackContext) != S_OK) + if(m_readFromTarget((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo), m_callbackContext) != S_OK) { return NULL; } @@ -371,12 +371,12 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) // Allocate a buffer for the unwind info from cDAC callback. // This buffer will be freed by the cDAC host once unwinding is done. UNWIND_INFO* pUnwindInfo; - if(getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo, callbackContext) != S_OK) + if(m_getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo, m_callbackContext) != S_OK) { return NULL; } - if(readFromTarget(taUnwindInfo, pUnwindInfo, cbUnwindInfo, callbackContext) != S_OK) + if(m_readFromTarget(taUnwindInfo, pUnwindInfo, cbUnwindInfo, m_callbackContext) != S_OK) { return NULL; } @@ -1333,7 +1333,7 @@ Routine Description: #ifndef FEATURE_CDAC_UNWINDER InstructionBuffer InstrBuffer = (InstructionBuffer)ControlPc; #else // FEATURE_CDAC_UNWINDER - InstructionBuffer InstrBuffer(ControlPc, readFromTarget, callbackContext); + InstructionBuffer InstrBuffer(ControlPc, m_readFromTarget, m_callbackContext); #endif // FEATURE_CDAC_UNWINDER InstructionBuffer NextByte = InstrBuffer; diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index 5cc708e9b02676..e26e3a3b13d169 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -178,7 +178,7 @@ template T cdacRead(uint64_t addr) { T t; - g_pUnwinder->readFromTarget(addr, &t, sizeof(t), g_pUnwinder->callbackContext); + g_pUnwinder->m_readFromTarget(addr, &t, sizeof(t), g_pUnwinder->m_callbackContext); return t; } #define MEMORY_READ_BYTE(params, addr) (cdacRead(addr)) diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index ba322b4e86e59f..bf12f7970f56bc 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -32,7 +32,7 @@ HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, #ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); #else // FEATURE_CDAC_UNWINDER - getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, callbackContext); + m_getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, m_callbackContext); #endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } @@ -72,12 +72,12 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres memcpy(pBuffer, pFuncEntry, cbBuffer); return S_OK; #else // FEATURE_CDAC_UNWINDER - getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry), callbackContext); + m_getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry), m_callbackContext); if (pFuncEntry == NULL) { return E_FAIL; } - if (readFromTarget((DWORD64)pFuncEntry, pBuffer, cbBuffer, callbackContext) != S_OK) + if (m_readFromTarget((DWORD64)pFuncEntry, pBuffer, cbBuffer, m_callbackContext) != S_OK) { return E_FAIL; } diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index 6e32861f959f68..b4ef5b73fd3051 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -59,21 +59,21 @@ class OOPStackUnwinder GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) - : readFromTarget(readFromTarget), - getAllocatedBuffer(getAllocatedBuffer), - getStackWalkInfo(getStackWalkInfo), - callbackContext(callbackContext) + : m_readFromTarget(readFromTarget), + m_getAllocatedBuffer(getAllocatedBuffer), + m_getStackWalkInfo(getStackWalkInfo), + m_callbackContext(callbackContext) { } public: // These functions pointers are marked public because they are called using // a global instance of OOPStackUnwinder in the ARM64 implementation. - ReadFromTarget readFromTarget; - GetAllocatedBuffer getAllocatedBuffer; - GetStackWalkInfo getStackWalkInfo; + ReadFromTarget m_readFromTarget; + GetAllocatedBuffer m_getAllocatedBuffer; + GetStackWalkInfo m_getStackWalkInfo; - void* callbackContext; + void* m_callbackContext; #endif // FEATURE_CDAC_UWNINDER }; From 90fd6073927e0ddcef5fd4d4c7e37c3882204bc3 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 15:40:15 -0500 Subject: [PATCH 36/63] implement cDAC native unwinder assertions through refactor using thread_local callbacks --- src/coreclr/unwinder/amd64/unwinder.cpp | 173 +++++++----------- src/coreclr/unwinder/amd64/unwinder.h | 28 +-- src/coreclr/unwinder/arm64/unwinder.cpp | 19 +- src/coreclr/unwinder/arm64/unwinder.h | 8 +- src/coreclr/unwinder/baseunwinder.cpp | 10 +- src/coreclr/unwinder/baseunwinder.h | 61 +++--- .../Contracts/StackWalk/Context/Unwinder.cs | 25 ++- 7 files changed, 143 insertions(+), 181 deletions(-) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 95cf51bb7e6291..1d2a17b4186dc5 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -26,7 +26,7 @@ typedef DPTR(M128A) PTR_M128A; // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) +ULONG64 MemoryRead64(PULONG64 addr) { return *dac_cast((TADDR)addr); } @@ -48,7 +48,7 @@ ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) +M128A MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } @@ -56,20 +56,20 @@ M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) // Read 64 bit unsigned value from the specified addres when the unwinder is build // for the cDAC. This triggers a callback to the cDAC host to read the memory from // the target process. -ULONG64 OOPStackUnwinderAMD64::MemoryRead64(PULONG64 addr) +ULONG64 MemoryRead64(PULONG64 addr) { ULONG64 value; - m_readFromTarget((uint64_t)addr, &value, sizeof(value), m_callbackContext); + t_pCallbacks->readFromTarget((uint64_t)addr, &value, sizeof(value), t_pCallbacks->callbackContext); return value; } // Read 128 bit value from the specified addres when the unwinder is build // for the cDAC. This triggers a callback to the cDAC host to read the memory from // the target process. -M128A OOPStackUnwinderAMD64::MemoryRead128(PM128A addr) +M128A MemoryRead128(PM128A addr) { M128A value; - m_readFromTarget((uint64_t)addr, &value, sizeof(value), m_callbackContext); + t_pCallbacks->readFromTarget((uint64_t)addr, &value, sizeof(value), t_pCallbacks->callbackContext); return value; } #endif // FEATURE_CDAC_UNWINDER @@ -88,14 +88,11 @@ class InstructionBuffer SIZE_T m_address; UCHAR m_buffer[32]; -#ifdef FEATURE_CDAC_UNWINDER - ReadFromTarget m_readFromTarget; - void* m_callbackContext; - // Load the instructions from the target process being debugged +#ifdef FEATURE_CDAC_UNWINDER HRESULT Load() { - HRESULT hr = m_readFromTarget(m_address, m_buffer, sizeof(m_buffer), m_callbackContext); + HRESULT hr = t_pCallbacks->readFromTarget(m_address, m_buffer, sizeof(m_buffer), t_pCallbacks->callbackContext); if (SUCCEEDED(hr)) { // TODO: Implement breakpoint patching for cDAC @@ -105,8 +102,6 @@ class InstructionBuffer return hr; } #else // FEATURE_CDAC_UNWINDER - - // Load the instructions from the target process being debugged HRESULT Load() { HRESULT hr = DacReadAll(TO_TADDR(m_address), m_buffer, sizeof(m_buffer), false); @@ -121,22 +116,14 @@ class InstructionBuffer return hr; } -#endif +#endif // FEATURE_CDAC_UNWINDER public: // Construct the InstructionBuffer for the given address in the target process -#ifdef FEATURE_CDAC_UNWINDER - InstructionBuffer(SIZE_T address, ReadFromTarget readFromTarget, void* callbackContext) - : m_offset(0), - m_address(address), - m_readFromTarget(readFromTarget), - m_callbackContext(callbackContext) -#else // FEATURE_CDAC_UNWINDER InstructionBuffer(SIZE_T address) : m_offset(0), m_address(address) -#endif // FEATURE_CDAC_UNWINDER { HRESULT hr = Load(); if (FAILED(hr)) @@ -251,8 +238,7 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) BOOL DacUnwindStackFrame(CONTEXT * pContext, KNONVOLATILE_CONTEXT_POINTERS* pContextPointers) { - OOPStackUnwinderAMD64 unwinder; - BOOL res = unwinder.Unwind(pContext); + BOOL res = OOPStackUnwinderAMD64::Unwind(pContext); if (res && pContextPointers) { @@ -265,91 +251,22 @@ BOOL DacUnwindStackFrame(CONTEXT * pContext, KNONVOLATILE_CONTEXT_POINTERS* pCon return res; } -//--------------------------------------------------------------------------------------- -// -// Unwind the given CONTEXT to the caller CONTEXT. The given CONTEXT will be overwritten. -// -// Arguments: -// pContext - in-out parameter storing the specified CONTEXT on entry and the unwound CONTEXT on exit -// -// Return Value: -// TRUE if the unwinding is successful -// - -BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) -{ - HRESULT hr = E_FAIL; - - ULONG64 uControlPC = (DWORD64)dac_cast(::GetIP(pContext)); - - // get the module base - ULONG64 uImageBase; - hr = GetModuleBase(uControlPC, &uImageBase); - if (FAILED(hr)) - { - return FALSE; - } - - // get the function entry - IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; - hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); - if (FAILED(hr)) - { - return FALSE; - } - - // call VirtualUnwind() to do the real work - ULONG64 EstablisherFrame; - hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); - - return (hr == S_OK); -} - #elif defined(FEATURE_CDAC_UNWINDER) -BOOL amd64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) +BOOL amd64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, UnwinderFail unwinderFail, void* callbackContext) { - HRESULT hr = E_FAIL; - - OOPStackUnwinderAMD64 unwinder { readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext }; - hr = unwinder.Unwind((CONTEXT*) pContext); - - return (hr == S_OK); -} - -BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) -{ - HRESULT hr = E_FAIL; - - ULONG64 uControlPC = pContext->Rip; - - // get the module base - ULONG64 uImageBase; - hr = GetModuleBase(uControlPC, &uImageBase); - if (FAILED(hr)) - { - return FALSE; - } - - // get the function entry - IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; - hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); - if (FAILED(hr)) - { - return FALSE; - } - - // call VirtualUnwind() to do the real work - ULONG64 EstablisherFrame; - hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + CDACCallbacks callbacks { readFromTarget, getAllocatedBuffer, getStackWalkInfo, unwinderFail, callbackContext }; + t_pCallbacks = &callbacks; + BOOL res = OOPStackUnwinderAMD64::Unwind((CONTEXT*) pContext); + t_pCallbacks = nullptr; - return (hr == S_OK); + return res; } UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) { UNWIND_INFO unwindInfo; - if(m_readFromTarget((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo), m_callbackContext) != S_OK) + if(t_pCallbacks->readFromTarget((uint64_t)taUnwindInfo, &unwindInfo, sizeof(unwindInfo), t_pCallbacks->callbackContext) != S_OK) { return NULL; } @@ -371,12 +288,12 @@ UNWIND_INFO * OOPStackUnwinderAMD64::GetUnwindInfo(TADDR taUnwindInfo) // Allocate a buffer for the unwind info from cDAC callback. // This buffer will be freed by the cDAC host once unwinding is done. UNWIND_INFO* pUnwindInfo; - if(m_getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo, m_callbackContext) != S_OK) + if(t_pCallbacks->getAllocatedBuffer(cbUnwindInfo, (void**)&pUnwindInfo, t_pCallbacks->callbackContext) != S_OK) { return NULL; } - if(m_readFromTarget(taUnwindInfo, pUnwindInfo, cbUnwindInfo, m_callbackContext) != S_OK) + if(t_pCallbacks->readFromTarget(taUnwindInfo, pUnwindInfo, cbUnwindInfo, t_pCallbacks->callbackContext) != S_OK) { return NULL; } @@ -457,8 +374,7 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( { PEXCEPTION_ROUTINE handlerRoutine; - OOPStackUnwinderAMD64 unwinder; - HRESULT res = unwinder.VirtualUnwind( + HRESULT res = OOPStackUnwinderAMD64::VirtualUnwind( HandlerType, ImageBase, ControlPc, @@ -476,6 +392,51 @@ PEXCEPTION_ROUTINE RtlVirtualUnwind_Unsafe( #endif // !DACCESS_COMPILE && !FEATURE_CDAC_UNWINDER +//--------------------------------------------------------------------------------------- +// +// Unwind the given CONTEXT to the caller CONTEXT. The given CONTEXT will be overwritten. +// +// Arguments: +// pContext - in-out parameter storing the specified CONTEXT on entry and the unwound CONTEXT on exit +// +// Return Value: +// TRUE if the unwinding is successful +// + +BOOL OOPStackUnwinderAMD64::Unwind(CONTEXT * pContext) +{ + HRESULT hr = E_FAIL; + + ULONG64 uControlPC = +#ifndef FEATURE_CDAC_UNWINDER + (DWORD64)dac_cast(::GetIP(pContext)); +#else // FEATURE_CDAC_UNWINDER + pContext->Rip; +#endif // FEATURE_CDAC_UNWINDER + + // get the module base + ULONG64 uImageBase; + hr = GetModuleBase(uControlPC, &uImageBase); + if (FAILED(hr)) + { + return FALSE; + } + + // get the function entry + IMAGE_RUNTIME_FUNCTION_ENTRY functionEntry; + hr = GetFunctionEntry(uControlPC, &functionEntry, sizeof(functionEntry)); + if (FAILED(hr)) + { + return FALSE; + } + + // call VirtualUnwind() to do the real work + ULONG64 EstablisherFrame; + hr = VirtualUnwind(0, uImageBase, uControlPC, &functionEntry, pContext, NULL, &EstablisherFrame, NULL, NULL); + + return (hr == S_OK); +} + // // // @@ -1330,11 +1291,7 @@ Routine Description: InEpilogue = FALSE; if (UnwindVersion < 2) { -#ifndef FEATURE_CDAC_UNWINDER InstructionBuffer InstrBuffer = (InstructionBuffer)ControlPc; -#else // FEATURE_CDAC_UNWINDER - InstructionBuffer InstrBuffer(ControlPc, m_readFromTarget, m_callbackContext); -#endif // FEATURE_CDAC_UNWINDER InstructionBuffer NextByte = InstrBuffer; // diff --git a/src/coreclr/unwinder/amd64/unwinder.h b/src/coreclr/unwinder/amd64/unwinder.h index bf1d6e28e44fe6..f7bc18e5cebc2e 100644 --- a/src/coreclr/unwinder/amd64/unwinder.h +++ b/src/coreclr/unwinder/amd64/unwinder.h @@ -13,6 +13,7 @@ EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, + UnwinderFail unwinderFail, void* callbackContext); #endif // FEATURE_CDAC_UNWINDER @@ -23,22 +24,15 @@ EXTERN_C __declspec(dllexport) BOOL amd64Unwind(void* pContext, class OOPStackUnwinderAMD64 : public OOPStackUnwinder { -#ifdef FEATURE_CDAC_UNWINDER -public: - OOPStackUnwinderAMD64(ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) - : OOPStackUnwinder(readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext) - { } -#endif // FEATURE_CDAC_UNWINDER - public: // Unwind the given CONTEXT to the caller CONTEXT. The CONTEXT will be overwritten. - BOOL Unwind(CONTEXT * pContext); + static BOOL Unwind(CONTEXT * pContext); // // Everything below comes from dbghelp.dll. // - HRESULT VirtualUnwind(_In_ DWORD HandlerType, + static HRESULT VirtualUnwind(_In_ DWORD HandlerType, _In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, @@ -50,16 +44,16 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder protected: - ULONG UnwindOpSlots(_In_ UNWIND_CODE UnwindCode); + static ULONG UnwindOpSlots(_In_ UNWIND_CODE UnwindCode); - HRESULT UnwindEpilogue(_In_ ULONG64 ImageBase, + static HRESULT UnwindEpilogue(_In_ ULONG64 ImageBase, _In_ ULONG64 ControlPc, _In_ ULONG EpilogueOffset, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, __inout PCONTEXT ContextRecord, __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers); - HRESULT UnwindPrologue(_In_ DWORD64 ImageBase, + static HRESULT UnwindPrologue(_In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc, _In_ DWORD64 FrameBase, _In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, @@ -67,20 +61,16 @@ class OOPStackUnwinderAMD64 : public OOPStackUnwinder __inout_opt PKNONVOLATILE_CONTEXT_POINTERS ContextPointers, _Outptr_ _PIMAGE_RUNTIME_FUNCTION_ENTRY *FinalFunctionEntry); - _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry + static _PIMAGE_RUNTIME_FUNCTION_ENTRY LookupPrimaryFunctionEntry (_In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, _In_ DWORD64 ImageBase); - _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction + static _PIMAGE_RUNTIME_FUNCTION_ENTRY SameFunction (_In_ _PIMAGE_RUNTIME_FUNCTION_ENTRY FunctionEntry, _In_ DWORD64 ImageBase, _In_ DWORD64 ControlPc); - UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); - - ULONG64 MemoryRead64(PULONG64 addr); - - M128A MemoryRead128(PM128A addr); + static UNWIND_INFO * GetUnwindInfo(TADDR taUnwindInfo); }; #endif // __unwinder_amd64_h__ diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index e26e3a3b13d169..ed4238c98a6bf6 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -178,7 +178,7 @@ template T cdacRead(uint64_t addr) { T t; - g_pUnwinder->m_readFromTarget(addr, &t, sizeof(t), g_pUnwinder->m_callbackContext); + t_pCallbacks->readFromTarget(addr, &t, sizeof(t), t_pCallbacks->callbackContext); return t; } #define MEMORY_READ_BYTE(params, addr) (cdacRead(addr)) @@ -2790,18 +2790,15 @@ BOOL DacUnwindStackFrame(T_CONTEXT *pContext, T_KNONVOLATILE_CONTEXT_POINTERS* p return res; } #elif defined(FEATURE_CDAC_UNWINDER) -OOPStackUnwinderArm64* g_pUnwinder; -BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) +BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, UnwinderFail unwinderFail, void* callbackContext) { - HRESULT hr = E_FAIL; - - OOPStackUnwinderArm64 unwinder { readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext }; - g_pUnwinder = &unwinder; - hr = unwinder.Unwind((T_CONTEXT*) pContext); - - g_pUnwinder = nullptr; + CDACCallbacks callbacks { readFromTarget, getAllocatedBuffer, getStackWalkInfo, unwinderFail, callbackContext }; + t_pCallbacks = &callbacks; + OOPStackUnwinderArm64 unwinder; + BOOL res = unwinder.Unwind((T_CONTEXT*) pContext); + t_pCallbacks = nullptr; - return (hr == S_OK); + return res; } #endif // FEATURE_CDAC_UNWINDER diff --git a/src/coreclr/unwinder/arm64/unwinder.h b/src/coreclr/unwinder/arm64/unwinder.h index 0371c9a16ff1b1..98f0a61b3137b1 100644 --- a/src/coreclr/unwinder/arm64/unwinder.h +++ b/src/coreclr/unwinder/arm64/unwinder.h @@ -12,6 +12,7 @@ EXTERN_C __declspec(dllexport) BOOL arm64Unwind(void* pContext, ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, + UnwinderFail unwinderFail, void* callbackContext); #endif // FEATURE_CDAC_UNWINDER @@ -22,13 +23,6 @@ EXTERN_C __declspec(dllexport) BOOL arm64Unwind(void* pContext, ReadFromTarget r class OOPStackUnwinderArm64 : public OOPStackUnwinder { -#ifdef FEATURE_CDAC_UNWINDER -public: - OOPStackUnwinderArm64(ReadFromTarget readFromTarget, GetAllocatedBuffer getAllocatedBuffer, GetStackWalkInfo getStackWalkInfo, void* callbackContext) - : OOPStackUnwinder(readFromTarget, getAllocatedBuffer, getStackWalkInfo, callbackContext) - { } -#endif // FEATURE_CDAC_UNWINDER - public: // Unwind the given CONTEXT to the caller CONTEXT. The CONTEXT will be overwritten. BOOL Unwind(T_CONTEXT * pContext); diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index bf12f7970f56bc..7473d57714764a 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -12,6 +12,10 @@ EXTERN_C void GetRuntimeStackWalkInfo(IN ULONG64 ControlPc, OUT UINT_PTR* pFuncEntry); #endif // FEATURE_CDAC_UNWINDER +#ifdef FEATURE_CDAC_UNWINDER +thread_local CDACCallbacks* t_pCallbacks; +#endif // FEATURE_CDAC_UNWINDER + //--------------------------------------------------------------------------------------- // // Given a control PC, return the base of the module it is in. For jitted managed code, this is the @@ -32,7 +36,7 @@ HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, #ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); #else // FEATURE_CDAC_UNWINDER - m_getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, m_callbackContext); +t_pCallbacks->getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, t_pCallbacks->callbackContext); #endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } @@ -72,12 +76,12 @@ HRESULT OOPStackUnwinder::GetFunctionEntry( DWORD64 addres memcpy(pBuffer, pFuncEntry, cbBuffer); return S_OK; #else // FEATURE_CDAC_UNWINDER - m_getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry), m_callbackContext); + t_pCallbacks->getStackWalkInfo(address, NULL, reinterpret_cast(&pFuncEntry), t_pCallbacks->callbackContext); if (pFuncEntry == NULL) { return E_FAIL; } - if (m_readFromTarget((DWORD64)pFuncEntry, pBuffer, cbBuffer, m_callbackContext) != S_OK) + if (t_pCallbacks->readFromTarget((DWORD64)pFuncEntry, pBuffer, cbBuffer, t_pCallbacks->callbackContext) != S_OK) { return E_FAIL; } diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index b4ef5b73fd3051..a17aab90d34d45 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -10,13 +10,37 @@ using ReadFromTarget = int (*)(uint64_t addr, void* pBuffer, int bufferSize, void* callbackContext); using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer, void* callbackContext); using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry, void* callbackContext); -#endif // FEATURE_CDAC_UNWINDER +using UnwinderFail = void (*)(); +class CDACCallbacks +{ +public: + CDACCallbacks(ReadFromTarget readFromTarget, + GetAllocatedBuffer getAllocatedBuffer, + GetStackWalkInfo getStackWalkInfo, + UnwinderFail unwinderFail, + void* callbackContext) + : readFromTarget(readFromTarget), + getAllocatedBuffer(getAllocatedBuffer), + getStackWalkInfo(getStackWalkInfo), + unwinderFail(unwinderFail), + callbackContext(callbackContext) + { } + + ReadFromTarget readFromTarget; + GetAllocatedBuffer getAllocatedBuffer; + GetStackWalkInfo getStackWalkInfo; + UnwinderFail unwinderFail; + void* callbackContext; +}; + +// thread_local used to access cDAC callbacks outside of unwinder. +extern thread_local CDACCallbacks* t_pCallbacks; +#endif // FEATURE_CDAC_UNWINDER // Report failure in the unwinder if the condition is FALSE #if defined(FEATURE_CDAC_UNWINDER) -// TODO: Add cDAC UNWINDER_ASSERT -#define UNWINDER_ASSERT(x) +#define UNWINDER_ASSERT(Condition) if (!(Condition)) t_pCallbacks->unwinderFail() #elif defined(DACCESS_COMPILE) #define UNWINDER_ASSERT(Condition) if (!(Condition)) DacError(CORDBG_E_TARGET_INCONSISTENT) #else // !DACCESS_COMPILE AND !FEATURE_CDAC_UNWINDER @@ -30,7 +54,7 @@ using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UIN // are actually borrowed from dbghelp.dll. (StackWalk64() is built on top of these classes.) We have ripped // out everything we don't need such as symbol lookup and various state, and keep just enough code to support // VirtualUnwind(). The managed debugging infrastructure can't call RtlVirtualUnwind() because it doesn't -// work from out-of-processr +// work from out-of-processor // // Notes: // To see what we have changed in the borrowed source, you can diff the original version and our version. @@ -44,38 +68,13 @@ class OOPStackUnwinder // Given a control PC, return the base of the module it is in. For jitted managed code, this is the // start of the code heap. - HRESULT GetModuleBase( DWORD64 address, + static HRESULT GetModuleBase( DWORD64 address, _Out_ PDWORD64 pdwBase); // Given a control PC, return the function entry of the functoin it is in. - HRESULT GetFunctionEntry( DWORD64 address, + static HRESULT GetFunctionEntry( DWORD64 address, _Out_writes_(cbBuffer) PVOID pBuffer, DWORD cbBuffer); - -#ifdef FEATURE_CDAC_UNWINDER -protected: - - OOPStackUnwinder(ReadFromTarget readFromTarget, - GetAllocatedBuffer getAllocatedBuffer, - GetStackWalkInfo getStackWalkInfo, - void* callbackContext) - : m_readFromTarget(readFromTarget), - m_getAllocatedBuffer(getAllocatedBuffer), - m_getStackWalkInfo(getStackWalkInfo), - m_callbackContext(callbackContext) - { } - - -public: - // These functions pointers are marked public because they are called using - // a global instance of OOPStackUnwinder in the ARM64 implementation. - ReadFromTarget m_readFromTarget; - GetAllocatedBuffer m_getAllocatedBuffer; - GetStackWalkInfo m_getStackWalkInfo; - - void* m_callbackContext; - -#endif // FEATURE_CDAC_UWNINDER }; #endif // __unwinder_h__ 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 59f71507790baf..701c2df0bb5e4c 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 @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; using System.Collections.Generic; +using System.Diagnostics; namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal static unsafe partial class Unwinder @@ -15,6 +16,7 @@ private static partial int ARM64Unwind( delegate* unmanaged readFromTarget, delegate* unmanaged getAllocatedBuffer, delegate* unmanaged getStackWalkInfo, + delegate* unmanaged unwinderFail, void* callbackContext); public static int ARM64Unwind( @@ -24,7 +26,13 @@ public static int ARM64Unwind( using CallbackContext callbackContext = new(target); GCHandle handle = GCHandle.Alloc(callbackContext); - int ret = ARM64Unwind(ref context, &ReadFromTarget, &GetAllocatedBuffer, &GetStackWalkInfo, GCHandle.ToIntPtr(handle).ToPointer()); + int ret = ARM64Unwind( + ref context, + &ReadFromTarget, + &GetAllocatedBuffer, + &GetStackWalkInfo, + &UnwinderFail, + GCHandle.ToIntPtr(handle).ToPointer()); handle.Free(); return ret; @@ -36,6 +44,7 @@ private static partial int AMD64Unwind( delegate* unmanaged readFromTarget, delegate* unmanaged getAllocatedBuffer, delegate* unmanaged getStackWalkInfo, + delegate* unmanaged unwinderFail, void* callbackContext); public static int AMD64Unwind( @@ -45,7 +54,13 @@ public static int AMD64Unwind( using CallbackContext callbackContext = new(target); GCHandle handle = GCHandle.Alloc(callbackContext); - int ret = AMD64Unwind(ref context, &ReadFromTarget, &GetAllocatedBuffer, &GetStackWalkInfo, GCHandle.ToIntPtr(handle).ToPointer()); + int ret = AMD64Unwind( + ref context, + &ReadFromTarget, + &GetAllocatedBuffer, + &GetStackWalkInfo, + &UnwinderFail, + GCHandle.ToIntPtr(handle).ToPointer()); handle.Free(); return ret; @@ -144,4 +159,10 @@ private static unsafe void GetStackWalkInfo(ulong controlPC, void* pModuleBase, Console.WriteLine($"GetStackWalkInfo failed: {ex}"); } } + + [UnmanagedCallersOnly] + private static void UnwinderFail() + { + Debug.Fail("Native unwinder assertion failure."); + } } From 93a8345955880c3a6c9d432de3c81c34019c00bd Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 15:46:59 -0500 Subject: [PATCH 37/63] clean up diff --- src/coreclr/unwinder/amd64/unwinder.cpp | 8 ++++---- src/coreclr/unwinder/baseunwinder.cpp | 2 +- src/coreclr/unwinder/stdafx.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 1d2a17b4186dc5..7022937eecfbe5 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -26,7 +26,7 @@ typedef DPTR(M128A) PTR_M128A; // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -ULONG64 MemoryRead64(PULONG64 addr) +static ULONG64 MemoryRead64(PULONG64 addr) { return *dac_cast((TADDR)addr); } @@ -48,7 +48,7 @@ ULONG64 MemoryRead64(PULONG64 addr) // If the memory read fails in the DAC mode, the failure is reported as an exception // via the DacError function. // -M128A MemoryRead128(PM128A addr) +static M128A MemoryRead128(PM128A addr) { return *dac_cast((TADDR)addr); } @@ -56,7 +56,7 @@ M128A MemoryRead128(PM128A addr) // Read 64 bit unsigned value from the specified addres when the unwinder is build // for the cDAC. This triggers a callback to the cDAC host to read the memory from // the target process. -ULONG64 MemoryRead64(PULONG64 addr) +static ULONG64 MemoryRead64(PULONG64 addr) { ULONG64 value; t_pCallbacks->readFromTarget((uint64_t)addr, &value, sizeof(value), t_pCallbacks->callbackContext); @@ -66,7 +66,7 @@ ULONG64 MemoryRead64(PULONG64 addr) // Read 128 bit value from the specified addres when the unwinder is build // for the cDAC. This triggers a callback to the cDAC host to read the memory from // the target process. -M128A MemoryRead128(PM128A addr) +static M128A MemoryRead128(PM128A addr) { M128A value; t_pCallbacks->readFromTarget((uint64_t)addr, &value, sizeof(value), t_pCallbacks->callbackContext); diff --git a/src/coreclr/unwinder/baseunwinder.cpp b/src/coreclr/unwinder/baseunwinder.cpp index 7473d57714764a..1af3255845acdf 100644 --- a/src/coreclr/unwinder/baseunwinder.cpp +++ b/src/coreclr/unwinder/baseunwinder.cpp @@ -36,7 +36,7 @@ HRESULT OOPStackUnwinder::GetModuleBase( DWORD64 address, #ifndef FEATURE_CDAC_UNWINDER GetRuntimeStackWalkInfo(address, reinterpret_cast(pdwBase), NULL); #else // FEATURE_CDAC_UNWINDER -t_pCallbacks->getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, t_pCallbacks->callbackContext); + t_pCallbacks->getStackWalkInfo(address, reinterpret_cast(pdwBase), NULL, t_pCallbacks->callbackContext); #endif // FEATURE_CDAC_UNWINDER return ((*pdwBase == 0) ? E_FAIL : S_OK); } diff --git a/src/coreclr/unwinder/stdafx.h b/src/coreclr/unwinder/stdafx.h index e0b75a561fb57a..e0dc4fe44b3813 100644 --- a/src/coreclr/unwinder/stdafx.h +++ b/src/coreclr/unwinder/stdafx.h @@ -14,12 +14,12 @@ #include #include #include -#else // !FEATURE_CDAC_UNWINDER +#else // FEATURE_CDAC_UNWINDER #include #include #include #include -#endif +#endif // FEATURE_CDAC_UNWINDER #ifdef DACCESS_COMPILE #include From 746f584d06d1967b0d263cc0036f5674467e5b11 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 16:02:09 -0500 Subject: [PATCH 38/63] use MSTypes --- src/coreclr/unwinder/baseunwinder.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/unwinder/baseunwinder.h b/src/coreclr/unwinder/baseunwinder.h index a17aab90d34d45..b0aaf19be134e7 100644 --- a/src/coreclr/unwinder/baseunwinder.h +++ b/src/coreclr/unwinder/baseunwinder.h @@ -7,10 +7,10 @@ #define __unwinder_h__ #ifdef FEATURE_CDAC_UNWINDER -using ReadFromTarget = int (*)(uint64_t addr, void* pBuffer, int bufferSize, void* callbackContext); -using GetAllocatedBuffer = int (*)(int bufferSize, void** ppBuffer, void* callbackContext); -using GetStackWalkInfo = void (*)(uint64_t controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry, void* callbackContext); -using UnwinderFail = void (*)(); +using ReadFromTarget = LONG (*)(ULONG64 addr, PVOID pBuffer, LONG bufferSize, PVOID callbackContext); +using GetAllocatedBuffer = LONG (*)(LONG bufferSize, PVOID* ppBuffer, PVOID callbackContext); +using GetStackWalkInfo = VOID (*)(ULONG64 controlPC, UINT_PTR* pModuleBase, UINT_PTR* pFuncEntry, PVOID callbackContext); +using UnwinderFail = VOID (*)(); class CDACCallbacks { From bd413616ba422275b8aa637d5bdd3b79d60ab2e7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 16:04:34 -0500 Subject: [PATCH 39/63] make comment inclusive of cDAC --- src/coreclr/unwinder/amd64/unwinder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/unwinder/amd64/unwinder.cpp b/src/coreclr/unwinder/amd64/unwinder.cpp index 7022937eecfbe5..57acbd30ab06e8 100644 --- a/src/coreclr/unwinder/amd64/unwinder.cpp +++ b/src/coreclr/unwinder/amd64/unwinder.cpp @@ -161,7 +161,7 @@ class InstructionBuffer } // Get the byte at the given index from the current position - // Invoke DacError if the index is out of the buffer + // Assert that the index is within the buffer UCHAR operator[](int index) { int realIndex = m_offset + index; From da0425ae66b00caf2d474902e6d813c10f5abbf2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 13 Feb 2025 17:37:28 -0500 Subject: [PATCH 40/63] convert to use IEnumerable --- docs/design/datacontracts/StackWalk.md | 20 +-- .../Contracts/IStackWalk.cs | 6 +- .../Contracts/StackWalk/FrameIterator.cs | 2 +- .../Contracts/StackWalk/StackWalk_1.cs | 114 +++++++----------- .../cdacreader/src/Legacy/ClrDataStackWalk.cs | 15 ++- 5 files changed, 64 insertions(+), 93 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index c35c5273ca5eec..aac54452db0a02 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -5,17 +5,13 @@ This contract encapsulates support for walking the stack of managed threads. ## APIs of contract ```csharp -public interface IStackWalkHandle { }; public interface IStackDataFrameHandle { }; ``` ```csharp // Creates a stack walk and returns a handle -IStackWalkHandle CreateStackWalk(ThreadData threadData); -// Iterates the stackWalkHandle to the next frame. If successful, returns true. Otherwise false. -bool Next(IStackWalkHandle stackWalkHandle); -// Gets the current frame from a stack walk and returns a IStackDataFrameHandle to it. -IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); +IEnumerable CreateStackWalk(ThreadData threadData); + // Gets the thread context at the given stack dataframe. byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle); // Gets the Frame address at the given stack dataframe. Returns TargetPointer.Null if the current dataframe does not have a valid Frame. @@ -150,21 +146,15 @@ This could take multiple iterations, each time returning a new context to the ca ### APIs -The majority of the contract's complexity comes from the stack walking algorithm implementation which is implemented and controlled through the following APIs. -These handle setting up and iterating the stackwalk state according to the algorithm detailed above. +The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`. +The `IEnumerable` return value is computed lazily. ```csharp -IStackWalkHandle CreateStackWalk(ThreadData threadData); -bool Next(IStackWalkHandle stackWalkHandle); +IEnumerable CreateStackWalk(ThreadData threadData); ``` The rest of the APIs convey state about the stack walk at a given point which fall out of the stack walking algorithm relatively simply. -`GetCurrentFrame` creates a copy of the stack walk state which remains constant even if the stack walk is iterated. -```csharp -IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle); -``` - `GetRawContext` Retrieves the raw Windows style thread context of the current frame as a byte array. The size and shape of the context is platform dependent. * On Windows the context is defined directly in Windows header `winnt.h`. See [CONTEXT structure](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context) for more info. diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index 8b8a10decf0e43..4fed8a815f9fe5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -2,19 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; -public interface IStackWalkHandle { }; public interface IStackDataFrameHandle { }; public interface IStackWalk : IContract { static string IContract.Name => nameof(StackWalk); - public virtual IStackWalkHandle CreateStackWalk(ThreadData threadData) => throw new NotImplementedException(); - public virtual bool Next(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); - public virtual IStackDataFrameHandle GetCurrentFrame(IStackWalkHandle stackWalkHandle) => throw new NotImplementedException(); + public virtual IEnumerable CreateStackWalk(ThreadData threadData) => throw new NotImplementedException(); public virtual byte[] GetRawContext(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); public virtual TargetPointer GetFrameAddress(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); } 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 index 4f25c1509269b8..3ef20699a973c5 100644 --- 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 @@ -43,7 +43,7 @@ public bool Next() return true; } - public bool TryUpdateContext(ref IPlatformAgnosticContext context) + public bool TryUpdateContext(IPlatformAgnosticContext context) { switch (GetFrameType(CurrentFrame)) { 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 4a02024aa3cc4d..01a8afbf822bf2 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 @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; +using System.Collections.Generic; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -33,60 +33,59 @@ public enum StackWalkState SW_SKIPPED_FRAME, } - internal struct StackDataFrameHandle : IStackDataFrameHandle - { - internal IPlatformAgnosticContext Context { get; init; } - internal StackWalkState State { get; init; } - internal TargetPointer FrameAddress { get; init; } - } + private record StackDataFrameHandle( + IPlatformAgnosticContext Context, + StackWalkState State, + TargetPointer FrameAddress) : IStackDataFrameHandle + { } - internal class StackWalkHandle : IStackWalkHandle + private class StackWalkData(IPlatformAgnosticContext context, StackWalkState state, FrameIterator frameIter) { - public StackWalkState state; - public IPlatformAgnosticContext context; - public FrameIterator frameIter; + public IPlatformAgnosticContext Context { get; set; } = context; + public StackWalkState State { get; set; } = state; + public FrameIterator FrameIter { get; set; } = frameIter; - public StackWalkHandle(IPlatformAgnosticContext context, FrameIterator frameIter, StackWalkState state) - { - this.context = context; - this.frameIter = frameIter; - this.state = state; - } + public StackDataFrameHandle ToDataFrame() => new(Context.Clone(), State, FrameIter.CurrentFrameAddress); } - IStackWalkHandle IStackWalk.CreateStackWalk(ThreadData threadData) + IEnumerable IStackWalk.CreateStackWalk(ThreadData threadData) { IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); - FillContextFromThread(ref context, threadData); + FillContextFromThread(context, threadData); StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; - return new StackWalkHandle(context, new(_target, threadData), state); + StackWalkData stackWalkData = new(context, state, new(_target, threadData)); + + yield return stackWalkData.ToDataFrame(); + + while (Next(stackWalkData)) + { + yield return stackWalkData.ToDataFrame(); + } } - bool IStackWalk.Next(IStackWalkHandle stackWalkHandle) + private bool Next(StackWalkData handle) { - StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); - - switch (handle.state) + switch (handle.State) { case StackWalkState.SW_FRAMELESS: try { - handle.context.Unwind(_target); + handle.Context.Unwind(_target); } catch { - handle.state = StackWalkState.SW_ERROR; + handle.State = StackWalkState.SW_ERROR; throw; } break; case StackWalkState.SW_SKIPPED_FRAME: - handle.frameIter.Next(); + handle.FrameIter.Next(); break; case StackWalkState.SW_FRAME: - handle.frameIter.TryUpdateContext(ref handle.context); - if (!handle.frameIter.IsInlineCallFrameWithActiveCall()) + handle.FrameIter.TryUpdateContext(handle.Context); + if (!handle.FrameIter.IsInlineCallFrameWithActiveCall()) { - handle.frameIter.Next(); + handle.FrameIter.Next(); } break; case StackWalkState.SW_ERROR: @@ -95,62 +94,51 @@ bool IStackWalk.Next(IStackWalkHandle stackWalkHandle) } UpdateState(handle); - return handle.state is not (StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE); + return handle.State is not (StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE); } - private void UpdateState(StackWalkHandle handle) + private void UpdateState(StackWalkData handle) { // If we are complete or in a bad state, no updating is required. - if (handle.state is StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE) + if (handle.State is StackWalkState.SW_ERROR or StackWalkState.SW_COMPLETE) { return; } - bool isManaged = IsManaged(handle.context.InstructionPointer, out _); - bool validFrame = handle.frameIter.IsValid(); + bool isManaged = IsManaged(handle.Context.InstructionPointer, out _); + bool validFrame = handle.FrameIter.IsValid(); if (isManaged) { - handle.state = StackWalkState.SW_FRAMELESS; + handle.State = StackWalkState.SW_FRAMELESS; if (CheckForSkippedFrames(handle)) { - handle.state = StackWalkState.SW_SKIPPED_FRAME; + handle.State = StackWalkState.SW_SKIPPED_FRAME; return; } } else { - handle.state = validFrame ? StackWalkState.SW_FRAME : StackWalkState.SW_COMPLETE; + handle.State = validFrame ? StackWalkState.SW_FRAME : StackWalkState.SW_COMPLETE; } } - private bool CheckForSkippedFrames(StackWalkHandle handle) + private bool CheckForSkippedFrames(StackWalkData handle) { // ensure we can find the caller context - Debug.Assert(IsManaged(handle.context.InstructionPointer, out _)); + Debug.Assert(IsManaged(handle.Context.InstructionPointer, out _)); // if there are no more Frames, vacuously false - if (!handle.frameIter.IsValid()) + if (!handle.FrameIter.IsValid()) { return false; } // get the caller context - IPlatformAgnosticContext parentContext = handle.context.Clone(); + IPlatformAgnosticContext parentContext = handle.Context.Clone(); parentContext.Unwind(_target); - return handle.frameIter.CurrentFrameAddress.Value < parentContext.StackPointer.Value; - } - - IStackDataFrameHandle IStackWalk.GetCurrentFrame(IStackWalkHandle stackWalkHandle) - { - StackWalkHandle handle = AssertCorrectHandle(stackWalkHandle); - return new StackDataFrameHandle - { - Context = handle.context.Clone(), - State = handle.state, - FrameAddress = handle.frameIter.CurrentFrameAddress, - }; + return handle.FrameIter.CurrentFrameAddress.Value < parentContext.StackPointer.Value; } byte[] IStackWalk.GetRawContext(IStackDataFrameHandle stackDataFrameHandle) @@ -182,27 +170,17 @@ private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle return false; } - private unsafe void FillContextFromThread(ref IPlatformAgnosticContext refContext, ThreadData threadData) + private unsafe void FillContextFromThread(IPlatformAgnosticContext context, ThreadData threadData) { - byte[] bytes = new byte[refContext.Size]; + byte[] bytes = new byte[context.Size]; Span buffer = new Span(bytes); - int hr = _target.GetThreadContext((uint)threadData.OSId.Value, refContext.DefaultContextFlags, refContext.Size, buffer); + int hr = _target.GetThreadContext((uint)threadData.OSId.Value, context.DefaultContextFlags, context.Size, buffer); if (hr != 0) { throw new InvalidOperationException($"GetThreadContext failed with hr={hr}"); } - refContext.FillFromBuffer(buffer); - } - - private static StackWalkHandle AssertCorrectHandle(IStackWalkHandle stackWalkHandle) - { - if (stackWalkHandle is not StackWalkHandle handle) - { - throw new ArgumentException("Invalid stack walk handle", nameof(stackWalkHandle)); - } - - return handle; + context.FillFromBuffer(buffer); } private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle stackDataFrameHandle) diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index f0050454b47789..c351796c33c8c3 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; @@ -18,7 +19,7 @@ internal sealed unsafe partial class ClrDataStackWalk : IXCLRDataStackWalk private readonly Target _target; private readonly IXCLRDataStackWalk? _legacyImpl; - private readonly IStackWalkHandle _stackWalkHandle; + private readonly IEnumerator _dataFrames; public ClrDataStackWalk(TargetPointer threadAddr, uint flags, Target target, IXCLRDataStackWalk? legacyImpl) { @@ -28,7 +29,11 @@ public ClrDataStackWalk(TargetPointer threadAddr, uint flags, Target target, IXC _legacyImpl = legacyImpl; ThreadData threadData = _target.Contracts.Thread.GetThreadData(_threadAddr); - _stackWalkHandle = _target.Contracts.StackWalk.CreateStackWalk(threadData); + _dataFrames = _target.Contracts.StackWalk.CreateStackWalk(threadData).GetEnumerator(); + + // IEnumerator begins before the first element. + // Call MoveNext() to set _dataFrames.Current to the first element. + _dataFrames.MoveNext(); } int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextSize, [MarshalUsing(CountElementName = "contextBufSize"), Out] byte[] contextBuf) @@ -36,7 +41,7 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* int hr = HResults.S_OK; IStackWalk sw = _target.Contracts.StackWalk; - IStackDataFrameHandle dataFrame = sw.GetCurrentFrame(_stackWalkHandle); + IStackDataFrameHandle dataFrame = _dataFrames.Current; byte[] context = sw.GetRawContext(dataFrame); if (context.Length > contextBufSize) hr = HResults.E_INVALIDARG; @@ -80,7 +85,7 @@ int IXCLRDataStackWalk.Next() int hr; try { - hr = _target.Contracts.StackWalk.Next(_stackWalkHandle) ? HResults.S_OK : HResults.S_FALSE; + hr = _dataFrames.MoveNext() ? HResults.S_OK : HResults.S_FALSE; } catch (System.Exception ex) { @@ -110,7 +115,7 @@ int IXCLRDataStackWalk.Request(uint reqCode, uint inBufferSize, byte* inBuffer, hr = HResults.E_INVALIDARG; IStackWalk sw = _target.Contracts.StackWalk; - IStackDataFrameHandle frameData = sw.GetCurrentFrame(_stackWalkHandle); + IStackDataFrameHandle frameData = _dataFrames.Current; TargetPointer frameAddr = sw.GetFrameAddress(frameData); *(ulong*)outBuffer = frameAddr.Value; hr = HResults.S_OK; From bf1743b2a25d50c092f8688858a96e0d59081a50 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 14 Feb 2025 17:06:28 -0500 Subject: [PATCH 41/63] rework docs to better explain skipped Frame check --- docs/design/datacontracts/StackWalk.md | 149 ++++++++++++++++++------- 1 file changed, 110 insertions(+), 39 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index aac54452db0a02..d76aece42731f9 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -59,16 +59,16 @@ The intuition for walking a managed stack is relatively simply: unwind managed p In reality, the actual algorithm is a little more complex fow two reasons. It requires pausing to return the current context and Frame at certain points and it checks for "skipped Frames" which can occur if an capital "F" Frame is allocated in a managed stack frame (e.g. an inlined P/Invoke call). +1. Setup + 1. Set the current context `currContext` to be the thread's context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. + 2. Create a stack of the thread's capital "F" Frames `frameStack`. +2. **Return the current context**. +3. While the `currContext` is in managed code or `frameStack` is not empty: + 1. If `currContext` is native code, pop the top Frame from `frameStack` update the context using the popped Frame. **Return the updated context** and **go to step 3**. + 2. If `frameStack` is not empty, check for skipped Frames. Peek `frameStack` to find a Frame `frame`. Compare the address of `frame` (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding current context one iteration). + If the address of the `frame` is less than the caller's stack pointer, **return the current context**, pop the top Frame from `frameStack`, and **go to step 3**. + 3. Unwind `currContext` using the Windows style unwinder. **Return the current context**. -1. Read the thread context. Fetched as part of the [ICorDebugDataTarget](https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugdatatarget-getthreadcontext-method) COM interface. -2. If the current IP is in managed code, use Windows style unwinding iteratively, returning the updated context at each iteration, until the IP is not in managed code. -3. Iterate each Frame in the linked list: - 1. Check the current Frame, if it can update the context, do so. - 2. For all Frame except `InlinedCallFrame` with an active call, go to the next the Frame. - 3. Return the current context. - 4. Check for skipped Frames by comparing the address of the current Frame (allocated on the stack) with the caller of the current context's stack pointer (found by unwinding current context one iteration). - If the address of the Frame is less than the caller's stack pointer, go to the next Frame, return the current context and repeat this step. - 5. If the context was updated and the new IP is in managed code, use Windows style unwinding iteratively, returning a new context each iteration, until the IP is not in managed code. #### Simple Example @@ -84,21 +84,15 @@ Given the following call stack and capital "F" Frames linked list, we can apply + +
``` -InlinedCallFrame -(Context = ) +SoftwareExceptionFrame + (Context = ) - || - \/ + || + \/ -InlinedCallFrame -(Context = ) +SoftwareExceptionFrame + (Context = ) - || - \/ + || + \/ - NULL + NULL TERMINATOR ``` ``` +Managed Call: ----------- + | Native | <- 's SP - | | |-----------| <- 's SP - | Managed | - |-----------| <- 's SP - | | - | Native | - | | - |-----------| <- 's SP - | | | | | Managed | | | - | | - |-----------| <- 's SP + |-----------| <- 's SP | | | Native | + | | @@ -114,35 +108,112 @@ SoftwareExceptionFrame || \/ -SoftwareExceptionFrame - (Context = ) + NULL TERMINATOR +``` - || - \/ +
- NULL TERMINATOR +1. (1) Set `currContext` to the thread context ``. Create a stack of Frames `frameStack`. +2. (2) Return the `currContext` which has the threads context. +3. (3) `currContext` is in unmanaged code (native) however, because `frameStack` is not empty, we begin processing the context. +4. (3.1) Since `currContext` is unmanaged. We pop the SoftwareExceptionFrame from `frameStack` and use it to update `currContext`. The SoftwareExceptionFrame is holding context `` which we set `currContext` to. Return the current context and go back to step 3. +5. (3) Now `currContext` is in managed code as shown by ``'s SP. Therefore, we begin to process the context. +6. (3.1) Since `currContext` is managed, skip step 3.1. +7. (3.2) Since `frameStack` is empty, we do not check for skipped Frames. +8. (3.3) Unwind `currContext` a single iteration to `` and return the current context. +9. (3) `currContext` is now at unmanaged (native) code and `frameStack` is empty. Therefore we are done. + +The following C# code could yield a stack similar to the example above: +```csharp +void foo() +{ + // Call native code or function that calls down to native. + Console.ReadLine(); + // Capture stack trace while inside native code. +} +``` + +#### Skipped Frame Example +The skipped Frame check is important when managed code calls managed code through an unmanaged boundary. +This occurs when calling a function marked with `[UnmanagedCallersOnly]` as an unmanaged delegate from a managed caller. +In this case, if we ignored the skipped Frame check we would miss the unmanaged boundary. + +Given the following call stack and capital "F" Frames linked list, we can apply the above algorithm. + + + + + + + +
Call Stack (growing down) Capital "F" Frames Linked List
+ +``` +Unmanaged Call: -X-X-X-X-X- +Managed Call: ----------- +InlinedCallFrame location: [ICF] + + | Managed | <- 's SP + - | | + | | + |-X-X-X-X-X-| <- 's SP + | [ICF] | + | Managed | + | | + |-----------| <- 's SP + | | + | Native | + + | | + | StackBase | +``` + + +``` +InlinedCallFrame + (Context = ) + + || + \/ + + NULL TERMINATOR ```
-* (1) We fetch the initial thread context `
`, with SP pointing to the top of the call stack. -* (2) In our example we can see that ``'s SP is pointing to a native frame on the stack. Therefore context `` is not pointing to managed code and we don't unwind. -In actuality, this is checked through the [ExecutionManager](./ExecutionManager.md) contract. -* (3.1) We look at the current (first) Frame, a SoftwareExceptionFrame with attached context ``. Update our working context to ``. -* (3.2) Since the current Frame is not an InlinedCallFrame, set the current Frame to the next Frame on the chain. -* (3.3) Return the current context and Frame. -* (3.4) The example has no skipped Frames so we skip this step. -* (3.5) Since we updated our context and our new context is in managed code, we use the Windows style unwinding tool to iteratively unwind until the context is no longer managed. -This could take multiple iterations, each time returning a new context to the caller. We end up with context `` which is the first context inside of a native call frame. -* (3.1) We look at the current Frame, another SoftwareExceptionFrame with attached context ``. Update our working context to ``. -* (3.2) Since the current Frame is not an InlinedCallFrame, set the current Frame to the next Frame (`NULL TERMINATOR`) on the chain. -* (3.3) Return the current context and Frame. -* (3.4) The example has no skipped Frames so we skip this step. -* (3.5) Again, we updated our context and our new context is in managed code. Therefore, we use the Windows style unwinding tool to iteratively unwind until the context is no longer managed. -This could take multiple iterations, each time returning a new context to the caller. We end up with context `` which is the first context inside of a native call frame. -* (3) Since our current context is in native code and the current Frame is the `NULL TERMINATOR`, we are done. +1. (1) Set `currContext` to the thread context ``. Create a stack of Frames `frameStack`. +2. (2) Return the `currContext` which has the threads context. +3. (3) Since `currContext` is in managed code, we begin to process the context. +4. (3.1) Since `currContext` is managed, skip step 3.1. +5. (3.2) Check for skipped Frames. Copy `currContext` into `parentContext` and unwind `parentContext` once using the Windows style unwinder. As seen from the call stack, unwinding `currContext=` will yield ``. We peek the top of `frameStack` and find an InlinedCallFrame (shown in call stack above as `[ICF]`). Since `parentContext`'s SP is greater than the address of `[ICF]` there are no skipped Frames. +6. (3.3) Unwind `currContext` a single iteration to `` and return the current context. +7. (3) Since `currContext` is still in managed code, we continue processing the context. +8. (3.1) Since `currContext` is managed, skip step 3.1. +9. (3.2) Check for skipped Frames. Copy `currContext` into `parentContext` and unwind `parentContext` once using the Windows style unwinder. As seen from the call stack, unwinding `currContext=` will yield ``. We peek the top of `frameStack` and find an InlinedCallFrame (shown in call stack above as `[ICF]`). This time the the address of `[ICF]` is less than `parentContext`'s SP. Therefore we return the current context then pop the InlinedCallFrame from `frameStack` which is now empty and return to step 3. +10. (3) Since `currContext` is still in managed code, we continue processing the context. +11. (3.1) Since `currContext` is managed, skip step 3.1. +12. (3.2) Since `frameStack` is empty, we do not check for skipped Frames. +13. (3.3) Unwind `currContext` a single iteration to `` and return the current context. +14. (3) `currContext` is now at unmanaged (native) code and `frameStack` is empty. Therefore we are done. + +The following C# code could yield a stack similar to the example above: +```csharp +void foo() +{ + var fptr = (delegate* unmanaged)&bar; + fptr(); +} + +[UnmanagedCallersOnly] +private static void bar() +{ + // Do something + // Capture stack trace while in here +} +``` ### APIs From 33e8d01003d1cdb47ca6abc007e1abb10335edd8 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 21 Feb 2025 14:43:49 -0500 Subject: [PATCH 42/63] clean up commits --- .../debug/runtimeinfo/datadescriptor.cpp | 2 + .../debug/runtimeinfo/datadescriptor.h | 29 ++++ src/coreclr/vm/frames.h | 15 ++ .../DataType.cs | 4 + .../Constants.cs | 2 + .../StackWalk/Context/AMD64Context.cs | 13 +- .../StackWalk/Context/ARM64Context.cs | 10 +- .../StackWalk/Context/ContextHolder.cs | 27 +++- .../Context/IPlatformAgnosticContext.cs | 4 +- .../{IContext.cs => IPlatformContext.cs} | 1 + .../FrameHandling/AMD64FrameHandler.cs | 105 +++++++++++++ .../FrameHandling/ARM64FrameHandler.cs | 105 +++++++++++++ .../StackWalk/FrameHandling/FrameIterator.cs | 139 ++++++++++++++++++ .../FrameHandling/IPlatformFrameHandler.cs | 19 +++ .../Contracts/StackWalk/FrameIterator.cs | 97 ------------ .../Data/Frames/DebuggerEval.cs | 22 +++ .../Data/Frames/FramedMethodFrame.cs | 20 +++ .../Data/Frames/FuncEvalFrame.cs | 23 +++ .../Data/Frames/InlinedCallFrame.cs | 2 +- .../Data/Frames/TransitionBlock.cs | 20 +++ .../cdacreader/src/Legacy/ClrDataStackWalk.cs | 12 ++ 21 files changed, 567 insertions(+), 104 deletions(-) rename src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/{IContext.cs => IPlatformContext.cs} (99%) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/IPlatformFrameHandler.cs delete mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DebuggerEval.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FramedMethodFrame.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FuncEvalFrame.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs 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 859428044fd8a9..58d215cd1603fd 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -594,6 +594,30 @@ 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 + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -620,6 +644,11 @@ CDAC_GLOBAL(FeatureCOMInterop, uint8, 1) #else CDAC_GLOBAL(FeatureCOMInterop, uint8, 0) #endif +#ifdef UNIX_AMD64_ABI +CDAC_GLOBAL(UnixAmd64ABI, uint8, 1) +#else +CDAC_GLOBAL(UnixAmd64ABI, uint8, 0) +#endif // See Object::GetGCSafeMethodTable #ifdef TARGET_64BIT CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 59749d47edd0a5..3514f0d33cc290 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -1151,6 +1151,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; @@ -1668,8 +1676,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 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 bed6a8dcae1991..974b6af5ed0725 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -82,8 +82,12 @@ public enum DataType MethodImpl, NativeCodeSlot, GCCoverageInfo, + TransitionBlock, + DebuggerEval, Frame, InlinedCallFrame, SoftwareExceptionFrame, + FramedMethodFrame, + FuncEvalFrame, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index c93e3620c9771e..aff84bcc6767b2 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -16,6 +16,8 @@ public static class Globals public const string FeatureCOMInterop = nameof(FeatureCOMInterop); public const string FeatureEHFunclets = nameof(FeatureEHFunclets); + public const string UnixAmd64ABI = nameof(UnixAmd64ABI); + public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); 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..2a61a25d6a7dcf 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 @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// AMD64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -internal struct AMD64Context : IPlatformContext +public struct AMD64Context : IPlatformContext { [Flags] public enum ContextFlagsValues : uint @@ -238,4 +238,15 @@ public void Unwind(Target target) [Register(RegisterType.Debug)] [FieldOffset(0x4c8)] public ulong LastExceptionFromRip; + + public override readonly string ToString() + { + return $"P1Home: {P1Home}, P2Home: {P2Home}, P3Home: {P3Home}, P4Home: {P4Home}, P5Home: {P5Home}, P6Home: {P6Home}, " + + $"ContextFlags: {ContextFlags}, MxCsr: {MxCsr}, Cs: {Cs}, Ds: {Ds}, Es: {Es}, Fs: {Fs}, Gs: {Gs}, Ss: {Ss}, " + + $"EFlags: {EFlags}, Dr0: {Dr0}, Dr1: {Dr1}, Dr2: {Dr2}, Dr3: {Dr3}, Dr6: {Dr6}, Dr7: {Dr7}, " + + $"Rax: {Rax}, Rcx: {Rcx}, Rdx: {Rdx}, Rbx: {Rbx}, Rsp: {Rsp}, Rbp: {Rbp}, Rsi: {Rsi}, Rdi: {Rdi}, " + + $"R8: {R8}, R9: {R9}, R10: {R10}, R11: {R11}, R12: {R12}, R13: {R13}, R14: {R14}, R15: {R15}, Rip: {Rip}, " + + $"DebugControl: {DebugControl}, LastBranchToRip: {LastBranchToRip}, LastBranchFromRip: {LastBranchFromRip}, " + + $"LastExceptionToRip: {LastExceptionToRip}, LastExceptionFromRip: {LastExceptionFromRip}"; + } } 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..74a99b084dadc6 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 @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// ARM64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -internal struct ARM64Context : IPlatformContext +public struct ARM64Context : IPlatformContext { [Flags] public enum ContextFlagsValues : uint @@ -239,4 +239,12 @@ public void Unwind(Target target) public unsafe fixed ulong Wvr[ARM64_MAX_WATCHPOINTS]; #endregion + + public override unsafe string ToString() + { + return $"ContextFlags: {ContextFlags}, Cpsr: {Cpsr}, X0: {X0}, X1: {X1}, X2: {X2}, X3: {X3}, X4: {X4}, X5: {X5}, X6: {X6}, X7: {X7}, " + + $"X8: {X8}, X9: {X9}, X10: {X10}, X11: {X11}, X12: {X12}, X13: {X13}, X14: {X14}, X15: {X15}, X16: {X16}, X17: {X17}, " + + $"X18: {X18}, X19: {X19}, X20: {X20}, X21: {X21}, X22: {X22}, X23: {X23}, X24: {X24}, X25: {X25}, X26: {X26}, X27: {X27}, " + + $"X28: {X28}, Fp: {Fp}, Lr: {Lr}, Sp: {Sp}, Pc: {Pc}, Fpcr: {Fpcr}, Fpsr: {Fpsr}"; + } } 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..bd8e595b1264c7 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 @@ -6,7 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -public class CotnextHolder : IPlatformAgnosticContext where T : unmanaged, IPlatformContext +public class ContextHolder : IPlatformAgnosticContext, IEquatable> where T : unmanaged, IPlatformContext { public T Context; @@ -42,7 +42,30 @@ 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 override string? ToString() => Context.ToString(); + public bool Equals(ContextHolder? other) + { + if (other is null) + { + return false; + } + + 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..a26cf3ec744751 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 @@ -28,10 +28,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/FrameHandling/AMD64FrameHandler.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs new file mode 100644 index 00000000000000..35fcd6aa8fe1e4 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class AMD64FrameHandler(Target target, ContextHolder contextHolder) : IPlatformFrameHandler +{ + private readonly Target _target = target; + private readonly ContextHolder _context = contextHolder; + + bool IPlatformFrameHandler.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 false; + } + + _context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; + _context.StackPointer = inlinedCallFrame.CallSiteSP; + _context.FramePointer = inlinedCallFrame.CalleeSavedFP; + + return true; + } + + bool IPlatformFrameHandler.HandleSoftwareExceptionFrame(SoftwareExceptionFrame softwareExceptionFrame) + { + ContextHolder otherContextHolder = new(); + otherContextHolder.ReadFromAddress(_target, softwareExceptionFrame.TargetContext); + + UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); + + _context.InstructionPointer = otherContextHolder.Context.InstructionPointer; + _context.StackPointer = otherContextHolder.Context.StackPointer; + + return true; + } + + bool IPlatformFrameHandler.HandleTransitionFrame(FramedMethodFrame framedMethodFrame, TransitionBlock transitionBlock, uint transitionBlockSize) + { + _context.InstructionPointer = transitionBlock.ReturnAddress; + _context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; + UpdateFromCalleeSavedRegisters(transitionBlock.CalleeSavedRegisters); + + return true; + } + + bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval debuggerEval) + { + // No context to update if we're doing a func eval from within exception processing. + if (debuggerEval.EvalDuringException) + { + return false; + } + _context.ReadFromAddress(_target, debuggerEval.TargetContext); + return true; + } + + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) + { + bool unixAmd64Abi = _target.ReadGlobal(Constants.Globals.UnixAmd64ABI) != 0; + // Order of registers is hardcoded in the runtime. See vm/amd64/cgencpu.h CalleeSavedRegisters + if (unixAmd64Abi) + { + _context.Context.R12 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 0); + _context.Context.R13 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 1); + _context.Context.R14 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 2); + _context.Context.R15 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 3); + _context.Context.Rbx = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 4); + _context.Context.Rbp = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 5); + } + else + { + _context.Context.Rdi = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 0); + _context.Context.Rsi = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 1); + _context.Context.Rbx = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 2); + _context.Context.Rbp = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 3); + _context.Context.R12 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 4); + _context.Context.R13 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 5); + _context.Context.R14 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 6); + _context.Context.R15 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 7); + } + } + + private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder otherContext) + { + bool unixAmd64Abi = _target.ReadGlobal(Constants.Globals.UnixAmd64ABI) != 0; + + if (!unixAmd64Abi) + { + _context.Context.Rdi = otherContext.Context.Rdi; + _context.Context.Rsi = otherContext.Context.Rsi; + } + + _context.Context.Rbx = otherContext.Context.Rbx; + _context.Context.Rbp = otherContext.Context.Rbp; + _context.Context.R12 = otherContext.Context.R12; + _context.Context.R13 = otherContext.Context.R13; + _context.Context.R14 = otherContext.Context.R14; + _context.Context.R15 = otherContext.Context.R15; + } +} 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..e8e241220f875b --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class ARM64FrameHandler(Target target, ContextHolder contextHolder) : IPlatformFrameHandler +{ + private readonly Target _target = target; + private readonly ContextHolder _context = contextHolder; + + bool IPlatformFrameHandler.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 false; + } + + _context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; + _context.StackPointer = inlinedCallFrame.CallSiteSP; + _context.FramePointer = inlinedCallFrame.CalleeSavedFP; + + _context.Context.X19 = 0; + _context.Context.X20 = 0; + _context.Context.X21 = 0; + _context.Context.X22 = 0; + _context.Context.X23 = 0; + _context.Context.X24 = 0; + _context.Context.X25 = 0; + _context.Context.X26 = 0; + _context.Context.X27 = 0; + _context.Context.X28 = 0; + + return true; + } + + bool IPlatformFrameHandler.HandleSoftwareExceptionFrame(SoftwareExceptionFrame softwareExceptionFrame) + { + ContextHolder otherContextHolder = new(); + otherContextHolder.ReadFromAddress(_target, softwareExceptionFrame.TargetContext); + + UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); + + _context.InstructionPointer = otherContextHolder.Context.InstructionPointer; + _context.StackPointer = otherContextHolder.Context.StackPointer; + + return true; + } + + public bool HandleTransitionFrame(FramedMethodFrame framedMethodFrame, TransitionBlock transitionBlock, uint transitionBlockSize) + { + _context.InstructionPointer = transitionBlock.ReturnAddress; + _context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; + UpdateFromCalleeSavedRegisters(transitionBlock.CalleeSavedRegisters); + + return true; + } + + bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval debuggerEval) + { + // No context to update if we're doing a func eval from within exception processing. + if (debuggerEval.EvalDuringException) + { + return false; + } + _context.ReadFromAddress(_target, debuggerEval.TargetContext); + return true; + } + + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) + { + // Order of registers is hardcoded in the runtime. See vm/arm64/cgencpu.h CalleeSavedRegisters + _context.Context.Fp = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 0); + _context.Context.Lr = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 1); + _context.Context.X19 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 2); + _context.Context.X20 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 3); + _context.Context.X21 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 4); + _context.Context.X22 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 5); + _context.Context.X23 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 6); + _context.Context.X24 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 7); + _context.Context.X25 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 8); + _context.Context.X26 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 9); + _context.Context.X27 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 10); + _context.Context.X28 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 11); + } + + private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder otherContext) + { + _context.Context.Fp = otherContext.Context.Fp; + _context.Context.Lr = otherContext.Context.Lr; + _context.Context.X19 = otherContext.Context.X19; + _context.Context.X20 = otherContext.Context.X20; + _context.Context.X21 = otherContext.Context.X21; + _context.Context.X22 = otherContext.Context.X22; + _context.Context.X23 = otherContext.Context.X23; + _context.Context.X24 = otherContext.Context.X24; + _context.Context.X25 = otherContext.Context.X25; + _context.Context.X26 = otherContext.Context.X26; + _context.Context.X27 = otherContext.Context.X27; + _context.Context.X28 = otherContext.Context.X28; + } +} 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..226029aa653563 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs @@ -0,0 +1,139 @@ +// 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; + +internal sealed class FrameIterator +{ + private enum FrameType + { + Unknown, + + InlinedCallFrame, + SoftwareExceptionFrame, + + /* Transition Frame Types */ + FramedMethodFrame, + CLRToCOMMethodFrame, + PInvokeCalliFrame, + PrestubMethodFrame, + StubDispatchFrame, + CallCountingHelperFrame, + ExternalMethodFrame, + DynamicHelperFrame, + + FuncEvalFrame, + } + + 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 FrameType.InlinedCallFrame: + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + return GetFrameHandler(context).HandleInlinedCallFrame(inlinedCallFrame); + + case FrameType.SoftwareExceptionFrame: + Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + return GetFrameHandler(context).HandleSoftwareExceptionFrame(softwareExceptionFrame); + + // 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: + Data.FramedMethodFrame framedMethodFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + 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"); + } + return GetFrameHandler(context).HandleTransitionFrame(framedMethodFrame, transitionBlock, transitionBlockSize); + + case FrameType.FuncEvalFrame: + Data.FuncEvalFrame funcEvalFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + Data.DebuggerEval debuggerEval = target.ProcessedData.GetOrAdd(funcEvalFrame.DebuggerEvalPtr); + return GetFrameHandler(context).HandleFuncEvalFrame(funcEvalFrame, debuggerEval); + default: + return false; + } + } + + 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.VPtr == 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..52881b9244a54f --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/IPlatformFrameHandler.cs @@ -0,0 +1,19 @@ +// 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. +/// Methods return true if the context was updated from the Frame. False otherwise. +/// +internal interface IPlatformFrameHandler +{ + public abstract bool HandleInlinedCallFrame(Data.InlinedCallFrame frame); + public abstract bool HandleSoftwareExceptionFrame(Data.SoftwareExceptionFrame frame); + public abstract bool HandleTransitionFrame(Data.FramedMethodFrame frame, Data.TransitionBlock transitionBlock, uint transitionBlockSize); + public abstract bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); +} 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/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/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/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/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..c5b39c56d8301d 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; @@ -56,6 +57,8 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* #if DEBUG if (_legacyImpl is not null) { + using StreamWriter streamWriter = new(new FileStream("C:\\Users\\maxcharlamb\\OneDrive - Microsoft\\Desktop\\out.txt", FileMode.Append)); + Console.SetOut(streamWriter); byte[] localContextBuf = new byte[contextBufSize]; int hrLocal = _legacyImpl.GetContext(contextFlags, contextBufSize, null, localContextBuf); Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); @@ -65,9 +68,18 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextStruct.FillFromBuffer(contextBuf); localContextStruct.FillFromBuffer(localContextBuf); + Console.WriteLine($"cDAC: {contextStruct}"); + Console.WriteLine($"DAC: {localContextStruct}"); + + streamWriter.Flush(); + + Debug.Assert(contextStruct.Equals(localContextStruct)); + 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}"); + + Console.SetOut(Console.Out); } #endif From 62b85f0246825f002dcac29dbb792a9e292b3665 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 27 Feb 2025 14:57:57 -0500 Subject: [PATCH 43/63] add docs --- docs/design/datacontracts/StackWalk.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index d76aece42731f9..26968bcdcfe9f0 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -41,11 +41,18 @@ 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 | 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 | +| `UnixAmd64ABI` | `uint8` | Denotes if the Unix amd64 ABI is used. This changes which registers are volatile. | Contracts used: | Contract Name | From 8b66ec24d58fe08114a5a2b339be99c679781a1d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 27 Feb 2025 14:58:05 -0500 Subject: [PATCH 44/63] fix merge issue --- .../Contracts/StackWalk/Context/IContext.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs 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/IContext.cs deleted file mode 100644 index 7a8c3bf1ebacb3..00000000000000 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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.Contracts.StackWalkHelpers; - -public interface IPlatformContext -{ - public abstract uint Size { get; } - public abstract uint DefaultContextFlags { get; } - - public TargetPointer StackPointer { get; set; } - public TargetPointer InstructionPointer { get; set; } - public TargetPointer FramePointer { get; set; } - public abstract void Unwind(Target target); -} From a7fad07044304c5baf449a65a7ce2412c584ed3d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 28 Feb 2025 16:09:06 -0500 Subject: [PATCH 45/63] clean up --- .../Contracts/StackWalk/FrameIterator.cs | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs 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; - } -} From 5b2c76a495408d6af0f90e9dedb8d7a1d92eea08 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 28 Feb 2025 16:09:25 -0500 Subject: [PATCH 46/63] support resumable frames --- .../debug/runtimeinfo/datadescriptor.h | 9 +++++++++ src/coreclr/vm/frames.h | 8 ++++++++ .../DataType.cs | 1 + .../FrameHandling/AMD64FrameHandler.cs | 6 ++++++ .../FrameHandling/ARM64FrameHandler.cs | 5 +++++ .../StackWalk/FrameHandling/FrameIterator.cs | 13 ++++++++++-- .../FrameHandling/IPlatformFrameHandler.cs | 1 + .../Data/Frames/ResumableFrame.cs | 20 +++++++++++++++++++ 8 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ResumableFrame.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index ff12ba5f3b6bb7..f594ef2ebcd317 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -670,6 +670,15 @@ CDAC_TYPE_FIELD(DebuggerEval, /*bool*/, EvalDuringException, offsetof(DebuggerEv 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) + +#endif // FEATURE_HIJACK + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index f215df34ef38d0..47f0d9bb91e721 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); }; 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 e01767c9a90e23..943e8e31423eeb 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -97,4 +97,5 @@ public enum DataType SoftwareExceptionFrame, FramedMethodFrame, FuncEvalFrame, + ResumableFrame, } 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 index 35fcd6aa8fe1e4..f09d7322076ce4 100644 --- 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 @@ -59,6 +59,12 @@ bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval return true; } + bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) + { + _context.ReadFromAddress(_target, frame.TargetContextPtr); + return true; + } + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) { bool unixAmd64Abi = _target.ReadGlobal(Constants.Globals.UnixAmd64ABI) != 0; 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 index e8e241220f875b..8cf92976c6663b 100644 --- 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 @@ -70,6 +70,11 @@ bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval return true; } + bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) + { + _context.ReadFromAddress(_target, frame.TargetContextPtr); + return true; + } private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) { // Order of registers is hardcoded in the runtime. See vm/arm64/cgencpu.h CalleeSavedRegisters 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 index 226029aa653563..299a15f2902c21 100644 --- 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 @@ -2,7 +2,6 @@ // 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; @@ -15,7 +14,7 @@ private enum FrameType InlinedCallFrame, SoftwareExceptionFrame, - /* Transition Frame Types */ + /* TransitionFrame Types */ FramedMethodFrame, CLRToCOMMethodFrame, PInvokeCalliFrame, @@ -26,6 +25,10 @@ private enum FrameType DynamicHelperFrame, FuncEvalFrame, + + /* ResumableFrame Types */ + ResumableFrame, + RedirectedThreadFrame, } private readonly Target target; @@ -90,6 +93,12 @@ public bool TryUpdateContext(IPlatformAgnosticContext context) Data.FuncEvalFrame funcEvalFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); Data.DebuggerEval debuggerEval = target.ProcessedData.GetOrAdd(funcEvalFrame.DebuggerEvalPtr); return GetFrameHandler(context).HandleFuncEvalFrame(funcEvalFrame, debuggerEval); + + // ResumableFrame type frames + case FrameType.ResumableFrame: + case FrameType.RedirectedThreadFrame: + Data.ResumableFrame resumableFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + return GetFrameHandler(context).HandleResumableFrame(resumableFrame); default: return false; } 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 index 52881b9244a54f..b2248247aa06d5 100644 --- 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 @@ -16,4 +16,5 @@ internal interface IPlatformFrameHandler public abstract bool HandleSoftwareExceptionFrame(Data.SoftwareExceptionFrame frame); public abstract bool HandleTransitionFrame(Data.FramedMethodFrame frame, Data.TransitionBlock transitionBlock, uint transitionBlockSize); public abstract bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); + public abstract bool HandleResumableFrame(Data.ResumableFrame frame); } 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; } +} From 17244c8af57a85ed7f742c63d9522a05b8b69247 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 3 Mar 2025 13:53:35 -0500 Subject: [PATCH 47/63] Support FaultingExceptionFrames --- .../debug/runtimeinfo/datadescriptor.h | 9 +++++-- src/coreclr/vm/frames.h | 10 ++++++++ .../DataType.cs | 1 + .../StackWalk/Context/AMD64Context.cs | 1 + .../StackWalk/Context/ARM64Context.cs | 3 +++ .../FrameHandling/AMD64FrameHandler.cs | 16 ++++++++++++ .../FrameHandling/ARM64FrameHandler.cs | 16 ++++++++++++ .../StackWalk/FrameHandling/FrameIterator.cs | 6 +++++ .../FrameHandling/IPlatformFrameHandler.cs | 1 + .../Data/Frames/FaultingExceptionFrame.cs | 25 +++++++++++++++++++ 10 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FaultingExceptionFrame.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index f594ef2ebcd317..1a1461937452aa 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -671,14 +671,19 @@ 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) - #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) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 47f0d9bb91e721..c6987fa7090580 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -1019,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 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 943e8e31423eeb..3d2ab13325c37b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -98,4 +98,5 @@ public enum DataType FramedMethodFrame, FuncEvalFrame, ResumableFrame, + FaultingExceptionFrame, } 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 2a61a25d6a7dcf..f6721a727e3b67 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,7 @@ 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 74a99b084dadc6..44397e37a50ff5 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/FrameHandling/AMD64FrameHandler.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs index f09d7322076ce4..258d69432c1be0 100644 --- 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 @@ -1,7 +1,9 @@ // 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; @@ -65,6 +67,20 @@ bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) return true; } + bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + if (frame.TargetContext is not TargetPointer targetContext) + { + throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); + } + _context.ReadFromAddress(_target, targetContext); + + // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + return true; + } + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) { bool unixAmd64Abi = _target.ReadGlobal(Constants.Globals.UnixAmd64ABI) != 0; 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 index 8cf92976c6663b..940caf24abcfb3 100644 --- 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 @@ -1,7 +1,9 @@ // 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.ARM64Context; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; @@ -70,6 +72,20 @@ bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval return true; } + bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + if (frame.TargetContext is not TargetPointer targetContext) + { + throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); + } + _context.ReadFromAddress(_target, targetContext); + + // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + return true; + } + bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) { _context.ReadFromAddress(_target, frame.TargetContextPtr); 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 index 299a15f2902c21..853bb6c4c0a2f2 100644 --- 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 @@ -29,6 +29,8 @@ private enum FrameType /* ResumableFrame Types */ ResumableFrame, RedirectedThreadFrame, + + FaultingExceptionFrame, } private readonly Target target; @@ -99,6 +101,10 @@ public bool TryUpdateContext(IPlatformAgnosticContext context) case FrameType.RedirectedThreadFrame: Data.ResumableFrame resumableFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); return GetFrameHandler(context).HandleResumableFrame(resumableFrame); + + case FrameType.FaultingExceptionFrame: + Data.FaultingExceptionFrame faultingExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + return GetFrameHandler(context).HandleFaultingExceptionFrame(faultingExceptionFrame); default: return false; } 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 index b2248247aa06d5..60ddfe9abffe39 100644 --- 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 @@ -17,4 +17,5 @@ internal interface IPlatformFrameHandler public abstract bool HandleTransitionFrame(Data.FramedMethodFrame frame, Data.TransitionBlock transitionBlock, uint transitionBlockSize); public abstract bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); public abstract bool HandleResumableFrame(Data.ResumableFrame frame); + public abstract bool HandleFaultingExceptionFrame(Data.FaultingExceptionFrame frame); } 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; } +} From ab01d2af1b9e537d7c196bf29bd198b6429f9181 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 3 Mar 2025 15:17:19 -0500 Subject: [PATCH 48/63] use datadescriptors to determine CalleeSavedRegisters --- .../debug/runtimeinfo/datadescriptor.h | 13 +++-- .../DataType.cs | 1 + .../Constants.cs | 2 - .../StackWalk/Context/ContextHolder.cs | 25 +++++++++- .../FrameHandling/AMD64FrameHandler.cs | 50 +++++++------------ .../FrameHandling/ARM64FrameHandler.cs | 46 ++++++++--------- .../Data/Frames/CalleeSavedRegisters.cs | 26 ++++++++++ 7 files changed, 96 insertions(+), 67 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/CalleeSavedRegisters.cs diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 1a1461937452aa..0b4f1d7157f4bc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -684,6 +684,14 @@ CDAC_TYPE_FIELD(FaultingExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data< #endif // FEATURE_EH_FUNCLETS CDAC_TYPE_END(FaultingExceptionFrame) +CDAC_TYPE_BEGIN(CalleeSavedRegisters) +CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters)) +#define CALLEE_SAVED_REGISTER(regname) \ + CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, regname, offsetof(CalleeSavedRegisters, regname)) +ENUM_CALLEE_SAVED_REGISTERS() +#undef CALLEE_SAVED_REGISTER +CDAC_TYPE_END(CalleeSavedRegisters) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -710,11 +718,6 @@ CDAC_GLOBAL(FeatureCOMInterop, uint8, 1) #else CDAC_GLOBAL(FeatureCOMInterop, uint8, 0) #endif -#ifdef UNIX_AMD64_ABI -CDAC_GLOBAL(UnixAmd64ABI, uint8, 1) -#else -CDAC_GLOBAL(UnixAmd64ABI, uint8, 0) -#endif // See Object::GetGCSafeMethodTable #ifdef TARGET_64BIT CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) 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 3d2ab13325c37b..26a43339956ff5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -91,6 +91,7 @@ public enum DataType GCCoverageInfo, TransitionBlock, DebuggerEval, + CalleeSavedRegisters, Frame, InlinedCallFrame, diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index f35a1c0c61c649..0e76ef91d7de39 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -16,8 +16,6 @@ public static class Globals public const string FeatureCOMInterop = nameof(FeatureCOMInterop); public const string FeatureEHFunclets = nameof(FeatureEHFunclets); - public const string UnixAmd64ABI = nameof(UnixAmd64ABI); - public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); public const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); 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 bd8e595b1264c7..1118b072149410 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 ContextHolder : IPlatformAgnosticContext, IEquatable> where T : unmanaged, IPlatformContext +public class ContextHolder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T> : IPlatformAgnosticContext, IEquatable> + where T : unmanaged, IPlatformContext { public T Context; @@ -46,6 +49,26 @@ public unsafe byte[] GetBytes() public void Clear() => Context = default; public void Unwind(Target target) => Context.Unwind(target); + public bool TrySetField(string fieldName, TargetNUInt value) + { + FieldInfo? field = typeof(T).GetField(fieldName); + if (field is null) return false; + field.SetValueDirect(__makeref(Context), value.Value); + return true; + } + + public bool TryReadField(string fieldName, out TargetNUInt value) + { + FieldInfo? field = typeof(T).GetField(fieldName); + if (field is null) + { + value = default; + return false; + } + value = new((ulong)field.GetValue(Context)!); + return true; + } + public override string? ToString() => Context.ToString(); public bool Equals(ContextHolder? other) { 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 index 258d69432c1be0..c1b961a48fa0c5 100644 --- 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 @@ -81,47 +81,31 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f return true; } - private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPtr) { - bool unixAmd64Abi = _target.ReadGlobal(Constants.Globals.UnixAmd64ABI) != 0; - // Order of registers is hardcoded in the runtime. See vm/amd64/cgencpu.h CalleeSavedRegisters - if (unixAmd64Abi) + Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); + foreach ((string name, TargetNUInt value) in calleeSavedRegisters.Registers) { - _context.Context.R12 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 0); - _context.Context.R13 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 1); - _context.Context.R14 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 2); - _context.Context.R15 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 3); - _context.Context.Rbx = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 4); - _context.Context.Rbp = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 5); - } - else - { - _context.Context.Rdi = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 0); - _context.Context.Rsi = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 1); - _context.Context.Rbx = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 2); - _context.Context.Rbp = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 3); - _context.Context.R12 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 4); - _context.Context.R13 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 5); - _context.Context.R14 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 6); - _context.Context.R15 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 7); + if (!_context.TrySetField(name, value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } } } private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder otherContext) { - bool unixAmd64Abi = _target.ReadGlobal(Constants.Globals.UnixAmd64ABI) != 0; - - if (!unixAmd64Abi) + foreach (string name in _target.GetTypeInfo(DataType.CalleeSavedRegisters).Fields.Keys) { - _context.Context.Rdi = otherContext.Context.Rdi; - _context.Context.Rsi = otherContext.Context.Rsi; - } + if (!otherContext.TryReadField(name, out TargetNUInt value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } + if (!_context.TrySetField(name, value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } - _context.Context.Rbx = otherContext.Context.Rbx; - _context.Context.Rbp = otherContext.Context.Rbp; - _context.Context.R12 = otherContext.Context.R12; - _context.Context.R13 = otherContext.Context.R13; - _context.Context.R14 = otherContext.Context.R14; - _context.Context.R15 = otherContext.Context.R15; + } } } 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 index 940caf24abcfb3..b736852bf7b7f8 100644 --- 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 @@ -91,36 +91,30 @@ bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) _context.ReadFromAddress(_target, frame.TargetContextPtr); return true; } - private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegisters) + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPtr) { - // Order of registers is hardcoded in the runtime. See vm/arm64/cgencpu.h CalleeSavedRegisters - _context.Context.Fp = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 0); - _context.Context.Lr = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 1); - _context.Context.X19 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 2); - _context.Context.X20 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 3); - _context.Context.X21 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 4); - _context.Context.X22 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 5); - _context.Context.X23 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 6); - _context.Context.X24 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 7); - _context.Context.X25 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 8); - _context.Context.X26 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 9); - _context.Context.X27 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 10); - _context.Context.X28 = _target.ReadPointer(calleeSavedRegisters + (ulong)_target.PointerSize * 11); + Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); + foreach ((string name, TargetNUInt value) in calleeSavedRegisters.Registers) + { + if (!_context.TrySetField(name, value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } + } } private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder otherContext) { - _context.Context.Fp = otherContext.Context.Fp; - _context.Context.Lr = otherContext.Context.Lr; - _context.Context.X19 = otherContext.Context.X19; - _context.Context.X20 = otherContext.Context.X20; - _context.Context.X21 = otherContext.Context.X21; - _context.Context.X22 = otherContext.Context.X22; - _context.Context.X23 = otherContext.Context.X23; - _context.Context.X24 = otherContext.Context.X24; - _context.Context.X25 = otherContext.Context.X25; - _context.Context.X26 = otherContext.Context.X26; - _context.Context.X27 = otherContext.Context.X27; - _context.Context.X28 = otherContext.Context.X28; + foreach (string name in _target.GetTypeInfo(DataType.CalleeSavedRegisters).Fields.Keys) + { + if (!otherContext.TryReadField(name, out TargetNUInt value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } + if (!_context.TrySetField(name, value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } + } } } 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; } +} From 8e6486677d06c7ad41669353cb08e3e70a35afe9 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 4 Mar 2025 12:56:54 -0500 Subject: [PATCH 49/63] support HijackFrames --- docs/design/datacontracts/StackWalk.md | 9 ++++- .../debug/runtimeinfo/datadescriptor.h | 36 +++++++++++++++++++ src/coreclr/vm/frames.h | 9 +++++ .../DataType.cs | 2 ++ .../FrameHandling/AMD64FrameHandler.cs | 22 ++++++++++++ .../FrameHandling/ARM64FrameHandler.cs | 29 ++++++++++++--- .../StackWalk/FrameHandling/FrameIterator.cs | 6 ++++ .../FrameHandling/IPlatformFrameHandler.cs | 1 + .../Data/Frames/HijackArgsAMD64.cs | 25 +++++++++++++ .../Data/Frames/HijackArgsARM64.cs | 27 ++++++++++++++ .../Data/Frames/HijackFrame.cs | 22 ++++++++++++ 11 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsAMD64.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 26968bcdcfe9f0..3b3c4bc62183a0 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -47,12 +47,19 @@ This contract depends on the following descriptors: | `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 | -| `UnixAmd64ABI` | `uint8` | Denotes if the Unix amd64 ABI is used. This changes which registers are volatile. | Contracts used: | Contract Name | diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 0b4f1d7157f4bc..7e00d3ff4f6ab6 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -675,6 +675,42 @@ 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, X30)) + +#endif +CDAC_TYPE_END(HijackArgs) #endif // FEATURE_HIJACK CDAC_TYPE_BEGIN(FaultingExceptionFrame) diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index c6987fa7090580..90e203cb1e0234 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -1996,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 26a43339956ff5..2941b7ca5cf7fe 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -92,6 +92,7 @@ public enum DataType TransitionBlock, DebuggerEval, CalleeSavedRegisters, + HijackArgs, Frame, InlinedCallFrame, @@ -100,4 +101,5 @@ public enum DataType FuncEvalFrame, ResumableFrame, FaultingExceptionFrame, + HijackFrame, } 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 index c1b961a48fa0c5..f7ffb9db09df01 100644 --- 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 @@ -81,6 +81,28 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f return true; } + bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + { + HijackArgsAMD64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + + _context.InstructionPointer = frame.ReturnAddress; + if (args.Rsp is TargetPointer rsp) + { + // Windows case, Rsp is passed directly + _context.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"); + _context.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + } + + UpdateFromCalleeSavedRegisters(args.CalleeSavedRegisters); + + return true; + } + private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPtr) { Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); 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 index b736852bf7b7f8..1d8d97c87631bb 100644 --- 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 @@ -2,6 +2,8 @@ // 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; @@ -56,7 +58,9 @@ public bool HandleTransitionFrame(FramedMethodFrame framedMethodFrame, Transitio { _context.InstructionPointer = transitionBlock.ReturnAddress; _context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; - UpdateFromCalleeSavedRegisters(transitionBlock.CalleeSavedRegisters); + + Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(transitionBlock.CalleeSavedRegisters); + UpdateFromRegisterDict(calleeSavedRegisters.Registers); return true; } @@ -91,10 +95,27 @@ bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) _context.ReadFromAddress(_target, frame.TargetContextPtr); return true; } - private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPtr) + + bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + { + HijackArgsARM64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + + _context.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; + _context.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + + UpdateFromRegisterDict(args.Registers); + return true; + } + + private void UpdateFromRegisterDict(IReadOnlyDictionary registers) { - Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); - foreach ((string name, TargetNUInt value) in calleeSavedRegisters.Registers) + foreach ((string name, TargetNUInt value) in registers) { if (!_context.TrySetField(name, value)) { 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 index 853bb6c4c0a2f2..71f4e4e1b125dd 100644 --- 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 @@ -31,6 +31,8 @@ private enum FrameType RedirectedThreadFrame, FaultingExceptionFrame, + + HijackFrame, } private readonly Target target; @@ -105,6 +107,10 @@ public bool TryUpdateContext(IPlatformAgnosticContext context) case FrameType.FaultingExceptionFrame: Data.FaultingExceptionFrame faultingExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); return GetFrameHandler(context).HandleFaultingExceptionFrame(faultingExceptionFrame); + + case FrameType.HijackFrame: + Data.HijackFrame hijackFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + return GetFrameHandler(context).HandleHijackFrame(hijackFrame); default: return false; } 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 index 60ddfe9abffe39..a4a4e4da24ec39 100644 --- 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 @@ -18,4 +18,5 @@ internal interface IPlatformFrameHandler public abstract bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); public abstract bool HandleResumableFrame(Data.ResumableFrame frame); public abstract bool HandleFaultingExceptionFrame(Data.FaultingExceptionFrame frame); + public abstract bool HandleHijackFrame(Data.HijackFrame frame); } 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; } +} From f5538745d685c4f5322a1271b418b02a8b1cf187 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 4 Mar 2025 13:25:49 -0500 Subject: [PATCH 50/63] fix datadescriptors --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 7e00d3ff4f6ab6..24cdfaa8461700 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -707,7 +707,7 @@ 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, X30)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Lr, offsetof(HijackArgs, Lr)) #endif CDAC_TYPE_END(HijackArgs) From 7c6be2db8ff2271ddd9b50115892421487403474 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 4 Mar 2025 13:57:28 -0500 Subject: [PATCH 51/63] fix CalleeSavedRegisters on non-amd64 --- .../debug/runtimeinfo/datadescriptor.h | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 24cdfaa8461700..4f7b6bef62d982 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -709,7 +709,7 @@ 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 +#endif // Platform switch CDAC_TYPE_END(HijackArgs) #endif // FEATURE_HIJACK @@ -720,12 +720,32 @@ CDAC_TYPE_FIELD(FaultingExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data< #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() From f55ba4e7fad4e7c92f1a97306045a5080917d4b6 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 4 Mar 2025 14:07:38 -0500 Subject: [PATCH 52/63] remove debugging helpers --- .../StackWalk/Context/AMD64Context.cs | 14 +---- .../StackWalk/Context/ARM64Context.cs | 10 +--- .../StackWalk/Context/ContextHolder.cs | 1 - .../FrameHandling/AMD64FrameHandler.cs | 34 ++++++------ .../FrameHandling/ARM64FrameHandler.cs | 54 +++++++++---------- .../cdacreader/src/Legacy/ClrDataStackWalk.cs | 14 ----- 6 files changed, 47 insertions(+), 80 deletions(-) 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 f6721a727e3b67..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 @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// AMD64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -public struct AMD64Context : IPlatformContext +internal struct AMD64Context : IPlatformContext { [Flags] public enum ContextFlagsValues : uint @@ -25,6 +25,7 @@ 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, } @@ -239,15 +240,4 @@ public void Unwind(Target target) [Register(RegisterType.Debug)] [FieldOffset(0x4c8)] public ulong LastExceptionFromRip; - - public override readonly string ToString() - { - return $"P1Home: {P1Home}, P2Home: {P2Home}, P3Home: {P3Home}, P4Home: {P4Home}, P5Home: {P5Home}, P6Home: {P6Home}, " + - $"ContextFlags: {ContextFlags}, MxCsr: {MxCsr}, Cs: {Cs}, Ds: {Ds}, Es: {Es}, Fs: {Fs}, Gs: {Gs}, Ss: {Ss}, " + - $"EFlags: {EFlags}, Dr0: {Dr0}, Dr1: {Dr1}, Dr2: {Dr2}, Dr3: {Dr3}, Dr6: {Dr6}, Dr7: {Dr7}, " + - $"Rax: {Rax}, Rcx: {Rcx}, Rdx: {Rdx}, Rbx: {Rbx}, Rsp: {Rsp}, Rbp: {Rbp}, Rsi: {Rsi}, Rdi: {Rdi}, " + - $"R8: {R8}, R9: {R9}, R10: {R10}, R11: {R11}, R12: {R12}, R13: {R13}, R14: {R14}, R15: {R15}, Rip: {Rip}, " + - $"DebugControl: {DebugControl}, LastBranchToRip: {LastBranchToRip}, LastBranchFromRip: {LastBranchFromRip}, " + - $"LastExceptionToRip: {LastExceptionToRip}, LastExceptionFromRip: {LastExceptionFromRip}"; - } } 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 44397e37a50ff5..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 @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// ARM64-specific thread context. /// [StructLayout(LayoutKind.Explicit, Pack = 1)] -public struct ARM64Context : IPlatformContext +internal struct ARM64Context : IPlatformContext { [Flags] public enum ContextFlagsValues : uint @@ -242,12 +242,4 @@ public void Unwind(Target target) public unsafe fixed ulong Wvr[ARM64_MAX_WATCHPOINTS]; #endregion - - public override unsafe string ToString() - { - return $"ContextFlags: {ContextFlags}, Cpsr: {Cpsr}, X0: {X0}, X1: {X1}, X2: {X2}, X3: {X3}, X4: {X4}, X5: {X5}, X6: {X6}, X7: {X7}, " + - $"X8: {X8}, X9: {X9}, X10: {X10}, X11: {X11}, X12: {X12}, X13: {X13}, X14: {X14}, X15: {X15}, X16: {X16}, X17: {X17}, " + - $"X18: {X18}, X19: {X19}, X20: {X20}, X21: {X21}, X22: {X22}, X23: {X23}, X24: {X24}, X25: {X25}, X26: {X26}, X27: {X27}, " + - $"X28: {X28}, Fp: {Fp}, Lr: {Lr}, Sp: {Sp}, Pc: {Pc}, Fpcr: {Fpcr}, Fpsr: {Fpsr}"; - } } 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 1118b072149410..5f1df39a7e794a 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 @@ -69,7 +69,6 @@ public bool TryReadField(string fieldName, out TargetNUInt value) return true; } - public override string? ToString() => Context.ToString(); public bool Equals(ContextHolder? other) { if (other is null) 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 index f7ffb9db09df01..9582626c7768f9 100644 --- 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 @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; internal class AMD64FrameHandler(Target target, ContextHolder contextHolder) : IPlatformFrameHandler { private readonly Target _target = target; - private readonly ContextHolder _context = contextHolder; + private readonly ContextHolder _holder = contextHolder; bool IPlatformFrameHandler.HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame) { @@ -21,9 +21,9 @@ bool IPlatformFrameHandler.HandleInlinedCallFrame(InlinedCallFrame inlinedCallFr return false; } - _context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; - _context.StackPointer = inlinedCallFrame.CallSiteSP; - _context.FramePointer = inlinedCallFrame.CalleeSavedFP; + _holder.InstructionPointer = inlinedCallFrame.CallerReturnAddress; + _holder.StackPointer = inlinedCallFrame.CallSiteSP; + _holder.FramePointer = inlinedCallFrame.CalleeSavedFP; return true; } @@ -35,16 +35,16 @@ bool IPlatformFrameHandler.HandleSoftwareExceptionFrame(SoftwareExceptionFrame s UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); - _context.InstructionPointer = otherContextHolder.Context.InstructionPointer; - _context.StackPointer = otherContextHolder.Context.StackPointer; + _holder.InstructionPointer = otherContextHolder.Context.InstructionPointer; + _holder.StackPointer = otherContextHolder.Context.StackPointer; return true; } bool IPlatformFrameHandler.HandleTransitionFrame(FramedMethodFrame framedMethodFrame, TransitionBlock transitionBlock, uint transitionBlockSize) { - _context.InstructionPointer = transitionBlock.ReturnAddress; - _context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; + _holder.InstructionPointer = transitionBlock.ReturnAddress; + _holder.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; UpdateFromCalleeSavedRegisters(transitionBlock.CalleeSavedRegisters); return true; @@ -57,13 +57,13 @@ bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval { return false; } - _context.ReadFromAddress(_target, debuggerEval.TargetContext); + _holder.ReadFromAddress(_target, debuggerEval.TargetContext); return true; } bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) { - _context.ReadFromAddress(_target, frame.TargetContextPtr); + _holder.ReadFromAddress(_target, frame.TargetContextPtr); return true; } @@ -73,11 +73,11 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f { throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); } - _context.ReadFromAddress(_target, targetContext); + _holder.ReadFromAddress(_target, targetContext); // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure // that does not support holding any extended state. - _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); return true; } @@ -85,17 +85,17 @@ bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) { HijackArgsAMD64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); - _context.InstructionPointer = frame.ReturnAddress; + _holder.InstructionPointer = frame.ReturnAddress; if (args.Rsp is TargetPointer rsp) { // Windows case, Rsp is passed directly - _context.StackPointer = rsp; + _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"); - _context.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; } UpdateFromCalleeSavedRegisters(args.CalleeSavedRegisters); @@ -108,7 +108,7 @@ private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPt Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); foreach ((string name, TargetNUInt value) in calleeSavedRegisters.Registers) { - if (!_context.TrySetField(name, value)) + if (!_holder.TrySetField(name, value)) { throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); } @@ -123,7 +123,7 @@ private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder contextHolder) : IPlatformFrameHandler { private readonly Target _target = target; - private readonly ContextHolder _context = contextHolder; + private readonly ContextHolder _holder = contextHolder; bool IPlatformFrameHandler.HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame) { @@ -23,20 +23,20 @@ bool IPlatformFrameHandler.HandleInlinedCallFrame(InlinedCallFrame inlinedCallFr return false; } - _context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; - _context.StackPointer = inlinedCallFrame.CallSiteSP; - _context.FramePointer = inlinedCallFrame.CalleeSavedFP; - - _context.Context.X19 = 0; - _context.Context.X20 = 0; - _context.Context.X21 = 0; - _context.Context.X22 = 0; - _context.Context.X23 = 0; - _context.Context.X24 = 0; - _context.Context.X25 = 0; - _context.Context.X26 = 0; - _context.Context.X27 = 0; - _context.Context.X28 = 0; + _holder.InstructionPointer = inlinedCallFrame.CallerReturnAddress; + _holder.StackPointer = inlinedCallFrame.CallSiteSP; + _holder.FramePointer = inlinedCallFrame.CalleeSavedFP; + + _holder.Context.X19 = 0; + _holder.Context.X20 = 0; + _holder.Context.X21 = 0; + _holder.Context.X22 = 0; + _holder.Context.X23 = 0; + _holder.Context.X24 = 0; + _holder.Context.X25 = 0; + _holder.Context.X26 = 0; + _holder.Context.X27 = 0; + _holder.Context.X28 = 0; return true; } @@ -48,16 +48,16 @@ bool IPlatformFrameHandler.HandleSoftwareExceptionFrame(SoftwareExceptionFrame s UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); - _context.InstructionPointer = otherContextHolder.Context.InstructionPointer; - _context.StackPointer = otherContextHolder.Context.StackPointer; + _holder.InstructionPointer = otherContextHolder.Context.InstructionPointer; + _holder.StackPointer = otherContextHolder.Context.StackPointer; return true; } public bool HandleTransitionFrame(FramedMethodFrame framedMethodFrame, TransitionBlock transitionBlock, uint transitionBlockSize) { - _context.InstructionPointer = transitionBlock.ReturnAddress; - _context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; + _holder.InstructionPointer = transitionBlock.ReturnAddress; + _holder.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(transitionBlock.CalleeSavedRegisters); UpdateFromRegisterDict(calleeSavedRegisters.Registers); @@ -72,7 +72,7 @@ bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval { return false; } - _context.ReadFromAddress(_target, debuggerEval.TargetContext); + _holder.ReadFromAddress(_target, debuggerEval.TargetContext); return true; } @@ -82,17 +82,17 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f { throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); } - _context.ReadFromAddress(_target, targetContext); + _holder.ReadFromAddress(_target, targetContext); // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure // that does not support holding any extended state. - _context.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); return true; } bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) { - _context.ReadFromAddress(_target, frame.TargetContextPtr); + _holder.ReadFromAddress(_target, frame.TargetContextPtr); return true; } @@ -100,14 +100,14 @@ bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) { HijackArgsARM64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); - _context.InstructionPointer = frame.ReturnAddress; + _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; - _context.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; UpdateFromRegisterDict(args.Registers); return true; @@ -117,7 +117,7 @@ private void UpdateFromRegisterDict(IReadOnlyDictionary reg { foreach ((string name, TargetNUInt value) in registers) { - if (!_context.TrySetField(name, value)) + if (!_holder.TrySetField(name, value)) { throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); } @@ -132,7 +132,7 @@ private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder Date: Thu, 6 Mar 2025 13:46:48 -0500 Subject: [PATCH 53/63] improve safety of context reflection --- .../StackWalk/Context/ContextHolder.cs | 38 +++++++++++++------ .../FrameHandling/AMD64FrameHandler.cs | 6 +-- .../FrameHandling/ARM64FrameHandler.cs | 6 +-- 3 files changed, 32 insertions(+), 18 deletions(-) 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 5f1df39a7e794a..367f6baa9be793 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 @@ -49,24 +49,38 @@ public unsafe byte[] GetBytes() public void Clear() => Context = default; public void Unwind(Target target) => Context.Unwind(target); - public bool TrySetField(string fieldName, TargetNUInt value) + public bool TrySetRegister(Target target, string fieldName, TargetNUInt value) { - FieldInfo? field = typeof(T).GetField(fieldName); - if (field is null) return false; - field.SetValueDirect(__makeref(Context), value.Value); - return true; + 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 TryReadField(string fieldName, out TargetNUInt value) + public bool TryReadRegister(Target target, string fieldName, out TargetNUInt value) { - FieldInfo? field = typeof(T).GetField(fieldName); - if (field is null) + value = default; + if (typeof(T).GetField(fieldName) is not FieldInfo field) return false; + if (field.GetValue(Context) is not object fieldValue) return false; + if (fieldValue is ulong ul && target.PointerSize == sizeof(ulong)) { - value = default; - return false; + value = new(ul); + return true; + } + if (fieldValue is uint ui && target.PointerSize == sizeof(uint)) + { + value = new(ui); + return true; } - value = new((ulong)field.GetValue(Context)!); - return true; + return false; } public bool Equals(ContextHolder? other) 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 index 9582626c7768f9..b6da4c7d3913ab 100644 --- 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 @@ -108,7 +108,7 @@ private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPt Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); foreach ((string name, TargetNUInt value) in calleeSavedRegisters.Registers) { - if (!_holder.TrySetField(name, value)) + if (!_holder.TrySetRegister(_target, name, value)) { throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); } @@ -119,11 +119,11 @@ private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder reg { foreach ((string name, TargetNUInt value) in registers) { - if (!_holder.TrySetField(name, value)) + if (!_holder.TrySetRegister(_target, name, value)) { throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); } @@ -128,11 +128,11 @@ private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder Date: Thu, 6 Mar 2025 13:53:16 -0500 Subject: [PATCH 54/63] fix small comments --- .../StackWalk/FrameHandling/ARM64FrameHandler.cs | 4 ++-- .../FrameHandling/IPlatformFrameHandler.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) 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 index 4501d1dc705815..0eb02815d2e605 100644 --- 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 @@ -84,7 +84,7 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f } _holder.ReadFromAddress(_target, targetContext); - // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure + // 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); return true; @@ -119,7 +119,7 @@ private void UpdateFromRegisterDict(IReadOnlyDictionary reg { if (!_holder.TrySetRegister(_target, name, value)) { - throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + throw new InvalidOperationException($"Unexpected register {name} found when trying to update context"); } } } 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 index a4a4e4da24ec39..fcd1ea5fbc6a8a 100644 --- 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 @@ -12,11 +12,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// internal interface IPlatformFrameHandler { - public abstract bool HandleInlinedCallFrame(Data.InlinedCallFrame frame); - public abstract bool HandleSoftwareExceptionFrame(Data.SoftwareExceptionFrame frame); - public abstract bool HandleTransitionFrame(Data.FramedMethodFrame frame, Data.TransitionBlock transitionBlock, uint transitionBlockSize); - public abstract bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); - public abstract bool HandleResumableFrame(Data.ResumableFrame frame); - public abstract bool HandleFaultingExceptionFrame(Data.FaultingExceptionFrame frame); - public abstract bool HandleHijackFrame(Data.HijackFrame frame); + bool HandleInlinedCallFrame(Data.InlinedCallFrame frame); + bool HandleSoftwareExceptionFrame(Data.SoftwareExceptionFrame frame); + bool HandleTransitionFrame(Data.FramedMethodFrame frame, Data.TransitionBlock transitionBlock, uint transitionBlockSize); + bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); + bool HandleResumableFrame(Data.ResumableFrame frame); + bool HandleFaultingExceptionFrame(Data.FaultingExceptionFrame frame); + bool HandleHijackFrame(Data.HijackFrame frame); } From a5dbba7c7b7cac59c9ff83939118fbb29fc35ee4 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Mar 2025 15:16:41 -0500 Subject: [PATCH 55/63] fix ARM64 inlinedcallframe --- .../StackWalk/FrameHandling/ARM64FrameHandler.cs | 11 ----------- 1 file changed, 11 deletions(-) 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 index 0eb02815d2e605..71986a3ae786b5 100644 --- 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 @@ -27,17 +27,6 @@ bool IPlatformFrameHandler.HandleInlinedCallFrame(InlinedCallFrame inlinedCallFr _holder.StackPointer = inlinedCallFrame.CallSiteSP; _holder.FramePointer = inlinedCallFrame.CalleeSavedFP; - _holder.Context.X19 = 0; - _holder.Context.X20 = 0; - _holder.Context.X21 = 0; - _holder.Context.X22 = 0; - _holder.Context.X23 = 0; - _holder.Context.X24 = 0; - _holder.Context.X25 = 0; - _holder.Context.X26 = 0; - _holder.Context.X27 = 0; - _holder.Context.X28 = 0; - return true; } From b234b77f828edf68d4637de4fa0713955f8ea8bf Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Mar 2025 15:58:26 -0500 Subject: [PATCH 56/63] frameIterator Next fix --- .../Contracts/StackWalk/FrameHandling/FrameIterator.cs | 2 +- .../Contracts/StackWalk/StackWalk_1.cs | 2 +- .../Data/Frames/Frame.cs | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) 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 index 71f4e4e1b125dd..8ef3853d4e7f2a 100644 --- 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 @@ -61,7 +61,7 @@ public bool Next() return false; currentFramePointer = CurrentFrame.Next; - return true; + return currentFramePointer != terminator; } public bool TryUpdateContext(IPlatformAgnosticContext context) 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/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; } } From 604ad500c215d5b2c10037d3fe98fb3e315b955d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Mar 2025 16:06:44 -0500 Subject: [PATCH 57/63] rework platform frame handlers to share common code --- .../Context/IPlatformAgnosticContext.cs | 2 + .../FrameHandling/AMD64FrameHandler.cs | 96 +---------------- .../FrameHandling/ARM64FrameHandler.cs | 92 +--------------- .../FrameHandling/BaseFrameHandler.cs | 100 ++++++++++++++++++ .../StackWalk/FrameHandling/FrameIterator.cs | 33 +++--- .../FrameHandling/IPlatformFrameHandler.cs | 15 ++- 6 files changed, 134 insertions(+), 204 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs 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 a26cf3ec744751..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) 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 index b6da4c7d3913ab..d5649e2a861994 100644 --- 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 @@ -7,67 +7,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -internal class AMD64FrameHandler(Target target, ContextHolder contextHolder) : IPlatformFrameHandler +internal class AMD64FrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler { - private readonly Target _target = target; private readonly ContextHolder _holder = contextHolder; - bool IPlatformFrameHandler.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 false; - } - - _holder.InstructionPointer = inlinedCallFrame.CallerReturnAddress; - _holder.StackPointer = inlinedCallFrame.CallSiteSP; - _holder.FramePointer = inlinedCallFrame.CalleeSavedFP; - - return true; - } - - bool IPlatformFrameHandler.HandleSoftwareExceptionFrame(SoftwareExceptionFrame softwareExceptionFrame) - { - ContextHolder otherContextHolder = new(); - otherContextHolder.ReadFromAddress(_target, softwareExceptionFrame.TargetContext); - - UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); - - _holder.InstructionPointer = otherContextHolder.Context.InstructionPointer; - _holder.StackPointer = otherContextHolder.Context.StackPointer; - - return true; - } - - bool IPlatformFrameHandler.HandleTransitionFrame(FramedMethodFrame framedMethodFrame, TransitionBlock transitionBlock, uint transitionBlockSize) - { - _holder.InstructionPointer = transitionBlock.ReturnAddress; - _holder.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; - UpdateFromCalleeSavedRegisters(transitionBlock.CalleeSavedRegisters); - - return true; - } - - bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval debuggerEval) - { - // No context to update if we're doing a func eval from within exception processing. - if (debuggerEval.EvalDuringException) - { - return false; - } - _holder.ReadFromAddress(_target, debuggerEval.TargetContext); - return true; - } - - bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) - { - _holder.ReadFromAddress(_target, frame.TargetContextPtr); - return true; - } - - bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) { if (frame.TargetContext is not TargetPointer targetContext) { @@ -78,10 +22,9 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f // 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); - return true; } - bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) { HijackArgsAMD64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); @@ -98,36 +41,7 @@ bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; } - UpdateFromCalleeSavedRegisters(args.CalleeSavedRegisters); - - return true; - } - - private void UpdateFromCalleeSavedRegisters(TargetPointer calleeSavedRegistersPtr) - { - Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(calleeSavedRegistersPtr); - foreach ((string name, TargetNUInt value) in calleeSavedRegisters.Registers) - { - if (!_holder.TrySetRegister(_target, name, value)) - { - throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); - } - } - } - - private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder 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 (!_holder.TrySetRegister(_target, name, value)) - { - throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); - } - - } + 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 index 71986a3ae786b5..59d8b055ba5078 100644 --- 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 @@ -9,63 +9,11 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -internal class ARM64FrameHandler(Target target, ContextHolder contextHolder) : IPlatformFrameHandler +internal class ARM64FrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler { - private readonly Target _target = target; private readonly ContextHolder _holder = contextHolder; - bool IPlatformFrameHandler.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 false; - } - - _holder.InstructionPointer = inlinedCallFrame.CallerReturnAddress; - _holder.StackPointer = inlinedCallFrame.CallSiteSP; - _holder.FramePointer = inlinedCallFrame.CalleeSavedFP; - - return true; - } - - bool IPlatformFrameHandler.HandleSoftwareExceptionFrame(SoftwareExceptionFrame softwareExceptionFrame) - { - ContextHolder otherContextHolder = new(); - otherContextHolder.ReadFromAddress(_target, softwareExceptionFrame.TargetContext); - - UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); - - _holder.InstructionPointer = otherContextHolder.Context.InstructionPointer; - _holder.StackPointer = otherContextHolder.Context.StackPointer; - - return true; - } - - public bool HandleTransitionFrame(FramedMethodFrame framedMethodFrame, TransitionBlock transitionBlock, uint transitionBlockSize) - { - _holder.InstructionPointer = transitionBlock.ReturnAddress; - _holder.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; - - Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(transitionBlock.CalleeSavedRegisters); - UpdateFromRegisterDict(calleeSavedRegisters.Registers); - - return true; - } - - bool IPlatformFrameHandler.HandleFuncEvalFrame(FuncEvalFrame frame, DebuggerEval debuggerEval) - { - // No context to update if we're doing a func eval from within exception processing. - if (debuggerEval.EvalDuringException) - { - return false; - } - _holder.ReadFromAddress(_target, debuggerEval.TargetContext); - return true; - } - - bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) { if (frame.TargetContext is not TargetPointer targetContext) { @@ -76,16 +24,9 @@ bool IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame f // 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); - return true; } - bool IPlatformFrameHandler.HandleResumableFrame(ResumableFrame frame) - { - _holder.ReadFromAddress(_target, frame.TargetContextPtr); - return true; - } - - bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) { HijackArgsARM64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); @@ -99,32 +40,5 @@ bool IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; UpdateFromRegisterDict(args.Registers); - return true; - } - - private void UpdateFromRegisterDict(IReadOnlyDictionary registers) - { - foreach ((string name, TargetNUInt value) in registers) - { - if (!_holder.TrySetRegister(_target, name, value)) - { - throw new InvalidOperationException($"Unexpected register {name} found when trying to update context"); - } - } - } - - private void UpdateCalleeSavedRegistersFromOtherContext(ContextHolder 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 (!_holder.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/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 index 8ef3853d4e7f2a..61e617ec835042 100644 --- 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 @@ -64,17 +64,19 @@ public bool Next() return currentFramePointer != terminator; } - public bool TryUpdateContext(IPlatformAgnosticContext context) + public void UpdateContextFromFrame(IPlatformAgnosticContext context) { switch (GetFrameType(CurrentFrame)) { case FrameType.InlinedCallFrame: Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - return GetFrameHandler(context).HandleInlinedCallFrame(inlinedCallFrame); + GetFrameHandler(context).HandleInlinedCallFrame(inlinedCallFrame); + return; case FrameType.SoftwareExceptionFrame: Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - return GetFrameHandler(context).HandleSoftwareExceptionFrame(softwareExceptionFrame); + GetFrameHandler(context).HandleSoftwareExceptionFrame(softwareExceptionFrame); + return; // TransitionFrame type frames case FrameType.FramedMethodFrame: @@ -86,33 +88,32 @@ public bool TryUpdateContext(IPlatformAgnosticContext context) case FrameType.ExternalMethodFrame: case FrameType.DynamicHelperFrame: Data.FramedMethodFrame framedMethodFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - 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"); - } - return GetFrameHandler(context).HandleTransitionFrame(framedMethodFrame, transitionBlock, transitionBlockSize); + GetFrameHandler(context).HandleTransitionFrame(framedMethodFrame); + return; case FrameType.FuncEvalFrame: Data.FuncEvalFrame funcEvalFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - Data.DebuggerEval debuggerEval = target.ProcessedData.GetOrAdd(funcEvalFrame.DebuggerEvalPtr); - return GetFrameHandler(context).HandleFuncEvalFrame(funcEvalFrame, debuggerEval); + GetFrameHandler(context).HandleFuncEvalFrame(funcEvalFrame); + return; // ResumableFrame type frames case FrameType.ResumableFrame: case FrameType.RedirectedThreadFrame: Data.ResumableFrame resumableFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - return GetFrameHandler(context).HandleResumableFrame(resumableFrame); + GetFrameHandler(context).HandleResumableFrame(resumableFrame); + return; case FrameType.FaultingExceptionFrame: Data.FaultingExceptionFrame faultingExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - return GetFrameHandler(context).HandleFaultingExceptionFrame(faultingExceptionFrame); + GetFrameHandler(context).HandleFaultingExceptionFrame(faultingExceptionFrame); + return; case FrameType.HijackFrame: Data.HijackFrame hijackFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - return GetFrameHandler(context).HandleHijackFrame(hijackFrame); + GetFrameHandler(context).HandleHijackFrame(hijackFrame); + return; default: - return false; + throw new InvalidOperationException($"Unknown frame type with identifier {CurrentFrame.Identifier}"); } } @@ -135,7 +136,7 @@ private FrameType GetFrameType(Data.Frame frame) { // not all Frames are in all builds, so we need to catch the exception typeVptr = target.ReadGlobalPointer(frameType.ToString() + "Identifier"); - if (frame.VPtr == typeVptr) + if (frame.Identifier == typeVptr) { return frameType; } 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 index fcd1ea5fbc6a8a..28e40d281763d2 100644 --- 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 @@ -8,15 +8,14 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; /// /// Interface for handling platform-specific frames. -/// Methods return true if the context was updated from the Frame. False otherwise. /// internal interface IPlatformFrameHandler { - bool HandleInlinedCallFrame(Data.InlinedCallFrame frame); - bool HandleSoftwareExceptionFrame(Data.SoftwareExceptionFrame frame); - bool HandleTransitionFrame(Data.FramedMethodFrame frame, Data.TransitionBlock transitionBlock, uint transitionBlockSize); - bool HandleFuncEvalFrame(Data.FuncEvalFrame frame, Data.DebuggerEval debuggerEval); - bool HandleResumableFrame(Data.ResumableFrame frame); - bool HandleFaultingExceptionFrame(Data.FaultingExceptionFrame frame); - bool HandleHijackFrame(Data.HijackFrame frame); + 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); } From e19df815d39117984154583768dcd7de26f4f195 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Mar 2025 16:16:16 -0500 Subject: [PATCH 58/63] do not throw on unknown Frame type --- .../Contracts/StackWalk/FrameHandling/FrameIterator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index 61e617ec835042..c496932afed7c0 100644 --- 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 @@ -113,7 +113,9 @@ public void UpdateContextFromFrame(IPlatformAgnosticContext context) GetFrameHandler(context).HandleHijackFrame(hijackFrame); return; default: - throw new InvalidOperationException($"Unknown frame type with identifier {CurrentFrame.Identifier}"); + // 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; } } From a95a851d27503eb02370ea218c91f76365c0d2b2 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Mar 2025 17:06:57 -0500 Subject: [PATCH 59/63] add docs for each type of supported Frame --- docs/design/datacontracts/StackWalk.md | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 3b3c4bc62183a0..02adbcc6c1a6a9 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -229,6 +229,69 @@ 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` a 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. + +#### 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`. From 0b118dc2c224187b0c51d85c98229cf494ae3d28 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 6 Mar 2025 17:12:00 -0500 Subject: [PATCH 60/63] doc typo --- docs/design/datacontracts/StackWalk.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 02adbcc6c1a6a9..774be93cdf7ea6 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -231,7 +231,9 @@ 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` a 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. +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 From d6c5a5bf8e6af78adc772dd770f867bb51ab42d7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 7 Mar 2025 14:39:06 -0500 Subject: [PATCH 61/63] fix unwinder for some scenarios --- .../Contracts/StackWalk/Context/Unwinder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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..a87ddf2b79044d 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 @@ -109,7 +109,15 @@ private static unsafe int ReadFromTarget(ulong address, void* pBuffer, int buffe return -1; } Span span = new Span(pBuffer, bufferSize); - callbackContext.Target.ReadBuffer(address, span); + try + { + callbackContext.Target.ReadBuffer(address, span); + } + catch (InvalidOperationException) + { + // if the read fails, the unwinder behavior changes. Return failing HR instead of throwing + return -1; + } return 0; } From 05c390de9125e78495711489f391a33457aa50ee Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 20 Mar 2025 13:57:15 -0400 Subject: [PATCH 62/63] address comments --- .../Contracts/StackWalk/Context/ContextHolder.cs | 9 ++------- .../Contracts/StackWalk/FrameHandling/FrameIterator.cs | 1 + 2 files changed, 3 insertions(+), 7 deletions(-) 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 367f6baa9be793..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 @@ -69,7 +69,7 @@ public bool TryReadRegister(Target target, string fieldName, out TargetNUInt val { value = default; if (typeof(T).GetField(fieldName) is not FieldInfo field) return false; - if (field.GetValue(Context) is not object fieldValue) return false; + object? fieldValue = field.GetValue(Context); if (fieldValue is ulong ul && target.PointerSize == sizeof(ulong)) { value = new(ul); @@ -85,12 +85,7 @@ public bool TryReadRegister(Target target, string fieldName, out TargetNUInt val public bool Equals(ContextHolder? other) { - if (other is null) - { - return false; - } - - if (GetType() != other.GetType()) + if (GetType() != other?.GetType()) { return false; } 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 index c496932afed7c0..b48a9e9b5433a9 100644 --- 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 @@ -87,6 +87,7 @@ public void UpdateContextFromFrame(IPlatformAgnosticContext context) 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; From 55c4cf63e050312fb2c1a3f5b073965068a5d11d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 20 Mar 2025 15:32:23 -0400 Subject: [PATCH 63/63] remove broken unwinder checks --- .../Contracts/StackWalk/Context/Unwinder.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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 a87ddf2b79044d..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,10 +104,7 @@ 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) - { - return -1; - } + CallbackContext callbackContext = (CallbackContext)GCHandle.FromIntPtr((IntPtr)context).Target!; Span span = new Span(pBuffer, bufferSize); try { @@ -127,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; @@ -145,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