| File: Contracts\StackWalk\StackWalk_1.ExceptionHandling.cs | Web Access |
| Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts) |
// 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.Diagnostics.CodeAnalysis; using System.Diagnostics; using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal partial class StackWalk_1 : IStackWalk { /// <summary> /// Flags from the ExceptionFlags class (exstatecommon.h). /// These are bit flags stored in ExInfo.m_ExceptionFlags.m_flags. /// </summary> [Flags] private enum ExceptionFlagsEnum : uint { // See Ex_UnwindHasStarted in src/coreclr/vm/exstatecommon.h UnwindHasStarted = 0x00000004, } /// <summary> /// Given the CrawlFrame for a funclet frame, return the frame pointer of the enclosing funclet frame. /// For filter funclet frames and normal method frames, this function returns a NULL StackFrame. /// </summary> /// <returns> /// StackFrame.IsNull() - no skipping is necessary /// StackFrame.IsMaxVal() - skip one frame and then ask again /// Anything else - skip to the method frame indicated by the return value and ask again /// </returns> private TargetPointer FindParentStackFrameForStackWalk(StackDataFrameHandle handle, bool forGCReporting = false) { if (!forGCReporting && IsFilterFunclet(handle)) { return TargetPointer.Null; } else { return FindParentStackFrameHelper(handle, forGCReporting); } } private TargetPointer FindParentStackFrameHelper( StackDataFrameHandle handle, bool forGCReporting = false) { IPlatformAgnosticContext callerContext = handle.Context.Clone(); callerContext.Unwind(_target); TargetPointer callerStackFrame = callerContext.StackPointer; bool isFilterFunclet = IsFilterFunclet(handle); // Check for out-of-line finally funclets. Filter funclets can't be out-of-line. if (!isFilterFunclet) { TargetCodePointer callerIp = callerContext.InstructionPointer; // In the runtime, on Windows, we check with that the IP is in the runtime // TODO(stackref): make sure this difference doesn't matter bool isCallerInVM = !IsManaged(callerIp, out CodeBlockHandle? _); if (!isCallerInVM) { if (!forGCReporting) { return TargetPointer.PlatformMaxValue(_target); } else { // ExInfo::GetCallerSPOfParentOfNonExceptionallyInvokedFunclet IPlatformAgnosticContext callerCallerContext = callerContext.Clone(); callerCallerContext.Unwind(_target); return callerCallerContext.StackPointer; } } } TargetPointer pExInfo = GetCurrentExceptionTracker(handle); while (pExInfo != TargetPointer.Null) { Data.ExceptionInfo exInfo = _target.ProcessedData.GetOrAdd<Data.ExceptionInfo>(pExInfo); pExInfo = exInfo.PreviousNestedInfo; // ExInfo::StackRange::IsEmpty if (exInfo.StackLowBound == TargetPointer.PlatformMaxValue(_target) && exInfo.StackHighBound == TargetPointer.Null) { // This is ExInfo has just been created, skip it. continue; } if (callerStackFrame == exInfo.CSFEHClause) { return exInfo.CSFEnclosingClause; } } return TargetPointer.Null; } // See https://github.com/dotnet/runtime/blob/5ad8ae4df419c33fed516bebe59231b21127bc5d/src/coreclr/vm/exceptionhandling.cpp#L3060 private bool TryGetFuncletParentInfo(StackDataFrameHandle funclet, out TargetPointer parentCallerSP, out uint parentNativeOffset) { parentCallerSP = TargetPointer.Null; parentNativeOffset = 0; // The funclet frame we are searching for in the fresh walk TargetPointer targetSP = funclet.Context.StackPointer; byte[] seed = funclet.Context.GetBytes(); bool foundTarget = false; TargetPointer skipTo = TargetPointer.Null; foreach (IStackDataFrameHandle rawHandle in ((IStackWalk)this).CreateStackWalk(funclet.ThreadData, seed, isFirst: true)) { StackDataFrameHandle cf = AssertCorrectHandle(rawHandle); // Only managed (frameless) frames participate if (cf.State != StackWalkState.Frameless) continue; if (!foundTarget) { if (cf.Context.StackPointer != targetSP) continue; foundTarget = true; } if (skipTo != TargetPointer.Null && (skipTo == TargetPointer.PlatformMaxValue(_target) || IsUnwoundToTargetParentFrame(cf, skipTo))) { // Reached the frame we were skipping to; clear and re-evaluate this frame. skipTo = TargetPointer.Null; } if (skipTo == TargetPointer.Null && IsFunclet(cf)) { // Current frame is itself a funclet (the target funclet or a nested one); // get its parent so we keep skipping. skipTo = FindParentStackFrameHelper(cf); } if (skipTo != TargetPointer.Null) continue; // We have reached the parent method frame. parentCallerSP = CallerStackPointer(cf); if (IsManaged(cf.Context.InstructionPointer, out CodeBlockHandle? cbh)) parentNativeOffset = (uint)_eman.GetRelativeOffset(cbh.Value).Value; return true; } return false; } private bool IsFunclet(StackDataFrameHandle handle) { // Only frames whose Context represents a managed method can be funclets. if (handle.State is not StackWalkState.Frameless) { return false; } if (!IsManaged(handle.Context.InstructionPointer, out CodeBlockHandle? cbh)) return false; return _eman.IsFunclet(cbh.Value); } private bool IsFilterFunclet(StackDataFrameHandle handle) { // Only frames whose Context represents a managed method can be filter funclets. if (handle.State is not StackWalkState.Frameless) { return false; } if (!IsManaged(handle.Context.InstructionPointer, out CodeBlockHandle? cbh)) return false; return _eman.IsFilterFunclet(cbh.Value); } private TargetPointer GetCurrentExceptionTracker(StackDataFrameHandle handle) { Data.Thread thread = _target.ProcessedData.GetOrAdd<Data.Thread>(handle.ThreadData.ThreadAddress); // ExceptionTracker is the address of the field on the Thread object. // Dereference to get the actual ExInfo pointer. return _target.ReadPointer(thread.ExceptionTracker); } private bool HasFrameBeenUnwoundByAnyActiveException(IStackDataFrameHandle stackDataFrameHandle) { StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); TargetPointer callerStackPointer; if (handle.State is StackWalkState.Frameless) { IPlatformAgnosticContext callerContext = handle.Context.Clone(); callerContext.Unwind(_target); callerStackPointer = callerContext.StackPointer; } else { callerStackPointer = handle.FrameAddress; } TargetPointer pExInfo = GetCurrentExceptionTracker(handle); while (pExInfo != TargetPointer.Null) { Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd<Data.ExceptionInfo>(pExInfo); pExInfo = exceptionInfo.PreviousNestedInfo; if (IsInStackRegionUnwoundBySpecifiedException(callerStackPointer, exceptionInfo)) return true; } return false; } private bool IsInStackRegionUnwoundBySpecifiedException(TargetPointer callerStackPointer, Data.ExceptionInfo exceptionInfo) { // The tracker must be in the second pass (unwind has started), and its stack range must not be empty. if ((exceptionInfo.ExceptionFlags & (uint)ExceptionFlagsEnum.UnwindHasStarted) == 0) return false; // Check for empty range if (exceptionInfo.StackLowBound == TargetPointer.PlatformMaxValue(_target) && exceptionInfo.StackHighBound == TargetPointer.Null) { return false; } return (exceptionInfo.StackLowBound < callerStackPointer) && (callerStackPointer <= exceptionInfo.StackHighBound); } }