summaryrefslogtreecommitdiff
path: root/src/vm/stackwalk.cpp
diff options
context:
space:
mode:
authorJiyoung Yun <jy910.yun@samsung.com>2016-11-23 19:09:09 +0900
committerJiyoung Yun <jy910.yun@samsung.com>2016-11-23 19:09:09 +0900
commit4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch)
tree98110734c91668dfdbb126fcc0e15ddbd93738ca /src/vm/stackwalk.cpp
parentfa45f57ed55137c75ac870356a1b8f76c84b229c (diff)
downloadcoreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/vm/stackwalk.cpp')
-rw-r--r--src/vm/stackwalk.cpp3493
1 files changed, 3493 insertions, 0 deletions
diff --git a/src/vm/stackwalk.cpp b/src/vm/stackwalk.cpp
new file mode 100644
index 0000000000..3b0b4720f7
--- /dev/null
+++ b/src/vm/stackwalk.cpp
@@ -0,0 +1,3493 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// STACKWALK.CPP
+
+
+
+#include "common.h"
+#include "frames.h"
+#include "threads.h"
+#include "stackwalk.h"
+#include "excep.h"
+#include "eetwain.h"
+#include "codeman.h"
+#include "eeconfig.h"
+#include "stackprobe.h"
+#include "dbginterface.h"
+#include "generics.h"
+#ifdef FEATURE_INTERPRETER
+#include "interpreter.h"
+#endif // FEATURE_INTERPRETER
+
+#ifdef _DEBUG
+void* forceFrame; // Variable used to force a local variable to the frame
+#endif
+
+CrawlFrame::CrawlFrame()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ pCurGSCookie = NULL;
+ pFirstGSCookie = NULL;
+ isCachedMethod = FALSE;
+}
+
+Assembly* CrawlFrame::GetAssembly()
+{
+ WRAPPER_NO_CONTRACT;
+
+ Assembly *pAssembly = NULL;
+ Frame *pF = GetFrame();
+
+ if (pF != NULL)
+ pAssembly = pF->GetAssembly();
+
+ if (pAssembly == NULL && pFunc != NULL)
+ pAssembly = pFunc->GetModule()->GetAssembly();
+
+ return pAssembly;
+}
+
+OBJECTREF* CrawlFrame::GetAddrOfSecurityObject()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+ if (isFrameless)
+ {
+ _ASSERTE(pFunc);
+
+#if defined(_TARGET_X86_)
+ if (isCachedMethod)
+ {
+ return pSecurityObject;
+ }
+ else
+#endif // _TARGET_X86_
+ {
+ return (static_cast <OBJECTREF*>(GetCodeManager()->GetAddrOfSecurityObject(this)));
+ }
+ }
+ else
+ {
+#ifdef FEATURE_INTERPRETER
+ // Check for an InterpreterFrame.
+ Frame* pFrm = GetFrame();
+ if (pFrm != NULL && pFrm->GetVTablePtr() == InterpreterFrame::GetMethodFrameVPtr())
+ {
+#ifdef DACCESS_COMPILE
+ // TBD: DACize the interpreter.
+ return NULL;
+#else
+ return dac_cast<PTR_InterpreterFrame>(pFrm)->GetInterpreter()->GetAddressOfSecurityObject();
+#endif
+ }
+ // Otherwise...
+#endif // FEATURE_INTERPRETER
+
+ /*ISSUE: Are there any other functions holding a security desc? */
+ if (pFunc && (pFunc->IsIL() || pFunc->IsNoMetadata()))
+ return dac_cast<PTR_FramedMethodFrame>
+ (pFrame)->GetAddrOfSecurityDesc();
+ }
+ return NULL;
+}
+
+BOOL CrawlFrame::IsInCalleesFrames(LPVOID stackPointer)
+{
+ LIMITED_METHOD_CONTRACT;
+#ifdef FEATURE_INTERPRETER
+ Frame* pFrm = GetFrame();
+ if (pFrm != NULL && pFrm->GetVTablePtr() == InterpreterFrame::GetMethodFrameVPtr())
+ {
+#ifdef DACCESS_COMPILE
+ // TBD: DACize the interpreter.
+ return NULL;
+#else
+ return dac_cast<PTR_InterpreterFrame>(pFrm)->GetInterpreter()->IsInCalleesFrames(stackPointer);
+#endif
+ }
+ else if (pFunc != NULL)
+ {
+ return ::IsInCalleesFrames(GetRegisterSet(), stackPointer);
+ }
+ else
+ {
+ return FALSE;
+ }
+#else
+ return ::IsInCalleesFrames(GetRegisterSet(), stackPointer);
+#endif
+}
+
+#ifdef FEATURE_INTERPRETER
+MethodDesc* CrawlFrame::GetFunction()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ STATIC_CONTRACT_SO_TOLERANT;
+ if (pFunc != NULL)
+ {
+ return pFunc;
+ }
+ else
+ {
+ Frame* pFrm = GetFrame();
+ if (pFrm != NULL && pFrm->GetVTablePtr() == InterpreterFrame::GetMethodFrameVPtr())
+ {
+#ifdef DACCESS_COMPILE
+ // TBD: DACize the interpreter.
+ return NULL;
+#else
+ return dac_cast<PTR_InterpreterFrame>(pFrm)->GetInterpreter()->GetMethodDesc();
+#endif
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+}
+#endif // FEATURE_INTERPRETER
+
+OBJECTREF CrawlFrame::GetThisPointer()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ if (!pFunc || pFunc->IsStatic() || pFunc->GetMethodTable()->IsValueType())
+ return NULL;
+
+ // As discussed in the specification comment at the declaration, the precondition, unfortunately,
+ // differs by architecture. @TODO: fix this.
+#if defined(_TARGET_X86_)
+ _ASSERTE_MSG((pFunc->IsSharedByGenericInstantiations() && pFunc->AcquiresInstMethodTableFromThis())
+ || pFunc->IsSynchronized(),
+ "Precondition");
+#else
+ _ASSERTE_MSG(pFunc->IsSharedByGenericInstantiations() && pFunc->AcquiresInstMethodTableFromThis(), "Precondition");
+#endif
+
+ if (isFrameless)
+ {
+ return GetCodeManager()->GetInstance(pRD,
+ &codeInfo);
+ }
+ else
+ {
+ _ASSERTE(pFrame);
+ _ASSERTE(pFunc);
+ /*ISSUE: we already know that we have (at least) a method */
+ /* might need adjustment as soon as we solved the
+ jit-helper frame question
+ */
+ //<TODO>@TODO: What about other calling conventions?
+// _ASSERT(pFunc()->GetCallSig()->CALLING CONVENTION);</TODO>
+
+#ifdef _TARGET_AMD64_
+ // @TODO: PORT: we need to find the this pointer without triggering a GC
+ // or find a way to make this method GC_TRIGGERS
+ return NULL;
+#else
+ return (dac_cast<PTR_FramedMethodFrame>(pFrame))->GetThis();
+#endif // _TARGET_AMD64_
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the "Ambient SP" from a CrawlFrame.
+// This will be null if there is no Ambient SP (eg, in the prolog / epilog,
+// or on certain platforms),
+//-----------------------------------------------------------------------------
+TADDR CrawlFrame::GetAmbientSPFromCrawlFrame()
+{
+ SUPPORTS_DAC;
+#if defined(_TARGET_X86_)
+ // we set nesting level to zero because it won't be used for esp-framed methods,
+ // and zero is at least valid for ebp based methods (where we won't use the ambient esp anyways)
+ DWORD nestingLevel = 0;
+ return GetCodeManager()->GetAmbientSP(
+ GetRegisterSet(),
+ GetCodeInfo(),
+ GetRelOffset(),
+ nestingLevel,
+ GetCodeManState()
+ );
+
+#elif defined(_TARGET_ARM_)
+ return GetRegisterSet()->pCurrentContext->Sp;
+#else
+ return NULL;
+#endif
+}
+
+
+PTR_VOID CrawlFrame::GetParamTypeArg()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ if (isFrameless)
+ {
+ return GetCodeManager()->GetParamTypeArg(pRD,
+ &codeInfo);
+ }
+ else
+ {
+#ifdef FEATURE_INTERPRETER
+ if (pFrame != NULL && pFrame->GetVTablePtr() == InterpreterFrame::GetMethodFrameVPtr())
+ {
+#ifdef DACCESS_COMPILE
+ // TBD: DACize the interpreter.
+ return NULL;
+#else
+ return dac_cast<PTR_InterpreterFrame>(pFrame)->GetInterpreter()->GetParamTypeArg();
+#endif
+ }
+ // Otherwise...
+#endif // FEATURE_INTERPRETER
+
+ if (!pFunc || !pFunc->RequiresInstArg())
+ {
+ return NULL;
+ }
+
+#ifdef _WIN64
+ if (!pFunc->IsSharedByGenericInstantiations() ||
+ !(pFunc->RequiresInstMethodTableArg() || pFunc->RequiresInstMethodDescArg()))
+ {
+ // win64 can only return the param type arg if the method is shared code
+ // and actually has a param type arg
+ return NULL;
+ }
+#endif // _WIN64
+
+ _ASSERTE(pFrame);
+ _ASSERTE(pFunc);
+ return (dac_cast<PTR_FramedMethodFrame>(pFrame))->GetParamTypeArg();
+ }
+}
+
+
+
+// [pClassInstantiation] : Always filled in, though may be set to NULL if no inst.
+// [pMethodInst] : Always filled in, though may be set to NULL if no inst.
+void CrawlFrame::GetExactGenericInstantiations(Instantiation *pClassInst,
+ Instantiation *pMethodInst)
+{
+
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pClassInst));
+ PRECONDITION(CheckPointer(pMethodInst));
+ } CONTRACTL_END;
+
+ TypeHandle specificClass;
+ MethodDesc* specificMethod;
+
+ BOOL ret = Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(
+ GetFunction(),
+ GetExactGenericArgsToken(),
+ &specificClass,
+ &specificMethod);
+
+ if (!ret)
+ {
+ _ASSERTE(!"Cannot return exact class instantiation when we are requested to.");
+ }
+
+ *pClassInst = specificMethod->GetExactClassInstantiation(specificClass);
+ *pMethodInst = specificMethod->GetMethodInstantiation();
+}
+
+PTR_VOID CrawlFrame::GetExactGenericArgsToken()
+{
+
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ MethodDesc* pFunc = GetFunction();
+
+ if (!pFunc || !pFunc->IsSharedByGenericInstantiations())
+ return NULL;
+
+ if (pFunc->AcquiresInstMethodTableFromThis())
+ {
+ OBJECTREF obj = GetThisPointer();
+ if (obj == NULL)
+ return NULL;
+ return obj->GetMethodTable();
+ }
+ else
+ {
+ _ASSERTE(pFunc->RequiresInstArg());
+ return GetParamTypeArg();
+ }
+}
+
+ /* Is this frame at a safe spot for GC?
+ */
+bool CrawlFrame::IsGcSafe()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ return GetCodeManager()->IsGcSafe(&codeInfo, GetRelOffset());
+}
+
+inline void CrawlFrame::GotoNextFrame()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ //
+ // Update app domain if this frame caused a transition
+ //
+
+ AppDomain *pRetDomain = pFrame->GetReturnDomain();
+ if (pRetDomain != NULL)
+ pAppDomain = pRetDomain;
+ pFrame = pFrame->Next();
+
+ if (pFrame != FRAME_TOP)
+ {
+ SetCurGSCookie(Frame::SafeGetGSCookiePtr(pFrame));
+ }
+}
+
+//******************************************************************************
+
+// For asynchronous stackwalks, the thread being walked may not be suspended.
+// It could cause a buffer-overrun while the stack-walk is in progress.
+// To detect this, we can only use data that is guarded by a GSCookie
+// that has been recently checked.
+// This function should be called after doing any time-consuming activity
+// during stack-walking to reduce the window in which a buffer-overrun
+// could cause an problems.
+//
+// To keep things simple, we do this checking even for synchronous stack-walks.
+void CrawlFrame::CheckGSCookies()
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+#if !defined(DACCESS_COMPILE)
+ if (pFirstGSCookie == NULL)
+ return;
+
+ if (*pFirstGSCookie != GetProcessGSCookie())
+ DoJITFailFast();
+
+ if(*pCurGSCookie != GetProcessGSCookie())
+ DoJITFailFast();
+#endif // !DACCESS_COMPILE
+}
+
+void CrawlFrame::SetCurGSCookie(GSCookie * pGSCookie)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+#if !defined(DACCESS_COMPILE)
+ if (pGSCookie == NULL)
+ DoJITFailFast();
+
+ pCurGSCookie = pGSCookie;
+ if (pFirstGSCookie == NULL)
+ pFirstGSCookie = pGSCookie;
+
+ CheckGSCookies();
+#endif // !DACCESS_COMPILE
+}
+
+#if defined(WIN64EXCEPTIONS)
+bool CrawlFrame::IsFilterFunclet()
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (!IsFrameless())
+ {
+ return false;
+ }
+
+ if (!isFilterFuncletCached)
+ {
+ isFilterFunclet = GetJitManager()->IsFilterFunclet(&codeInfo) != 0;
+ isFilterFuncletCached = true;
+ }
+
+ return isFilterFunclet;
+}
+
+#endif // WIN64EXCEPTIONS
+
+//******************************************************************************
+#if defined(ELIMINATE_FEF)
+//******************************************************************************
+// Advance to the next ExInfo. Typically done when an ExInfo has been used and
+// should not be used again.
+//******************************************************************************
+void ExInfoWalker::WalkOne()
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ if (m_pExInfo)
+ {
+ LOG((LF_EH, LL_INFO10000, "ExInfoWalker::WalkOne: advancing ExInfo chain: pExInfo:%p, pContext:%p; prev:%p, pContext:%p\n",
+ m_pExInfo, m_pExInfo->m_pContext, m_pExInfo->m_pPrevNestedInfo, m_pExInfo->m_pPrevNestedInfo?m_pExInfo->m_pPrevNestedInfo->m_pContext:0));
+ m_pExInfo = m_pExInfo->m_pPrevNestedInfo;
+ }
+} // void ExInfoWalker::WalkOne()
+
+//******************************************************************************
+// Attempt to find an ExInfo with a pContext that is higher (older) than
+// a given minimum location. (It is the pContext's SP that is relevant.)
+//******************************************************************************
+void ExInfoWalker::WalkToPosition(
+ TADDR taMinimum, // Starting point of stack walk.
+ BOOL bPopFrames) // If true, ResetUseExInfoForStackwalk on each exinfo.
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ while (m_pExInfo &&
+ ((GetSPFromContext() < taMinimum) ||
+ (GetSPFromContext() == NULL)) )
+ {
+ // Try the next ExInfo, if there is one.
+ LOG((LF_EH, LL_INFO10000,
+ "ExInfoWalker::WalkToPosition: searching ExInfo chain: m_pExInfo:%p, pContext:%p; \
+ prev:%p, pContext:%p; pStartFrame:%p\n",
+ m_pExInfo,
+ m_pExInfo->m_pContext,
+ m_pExInfo->m_pPrevNestedInfo,
+ (m_pExInfo->m_pPrevNestedInfo ? m_pExInfo->m_pPrevNestedInfo->m_pContext : 0),
+ taMinimum));
+
+ if (bPopFrames)
+ { // If caller asked for it, reset the bit which indicates that this ExInfo marks a fault from managed code.
+ // This is done so that the fault can be effectively "unwound" from the stack, similarly to how Frames
+ // are unlinked from the Frame chain.
+ m_pExInfo->m_ExceptionFlags.ResetUseExInfoForStackwalk();
+ }
+ m_pExInfo = m_pExInfo->m_pPrevNestedInfo;
+ }
+ // At this point, m_pExInfo is NULL, or points to a pContext that is greater than taMinimum.
+} // void ExInfoWalker::WalkToPosition()
+
+//******************************************************************************
+// Attempt to find an ExInfo with a pContext that has an IP in managed code.
+//******************************************************************************
+void ExInfoWalker::WalkToManaged()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ MODE_ANY;
+ SUPPORTS_DAC;
+ }
+ CONTRACTL_END;
+
+
+ while (m_pExInfo)
+ {
+ // See if the current ExInfo has a CONTEXT that "returns" to managed code, and, if so, exit the loop.
+ if (m_pExInfo->m_ExceptionFlags.UseExInfoForStackwalk() &&
+ GetContext() &&
+ ExecutionManager::IsManagedCode(GetIP(GetContext())))
+ {
+ break;
+ }
+ // No, so skip to next, if any.
+ LOG((LF_EH, LL_INFO1000, "ExInfoWalker::WalkToManaged: searching for ExInfo->managed: m_pExInfo:%p, pContext:%p, sp:%p; prev:%p, pContext:%p\n",
+ m_pExInfo,
+ GetContext(),
+ GetSPFromContext(),
+ m_pExInfo->m_pPrevNestedInfo,
+ m_pExInfo->m_pPrevNestedInfo?m_pExInfo->m_pPrevNestedInfo->m_pContext:0));
+ m_pExInfo = m_pExInfo->m_pPrevNestedInfo;
+ }
+ // At this point, m_pExInfo is NULL, or points to a pContext that has an IP in managed code.
+} // void ExInfoWalker::WalkToManaged()
+#endif // defined(ELIMINATE_FEF)
+
+#ifdef WIN64EXCEPTIONS
+// static
+UINT_PTR Thread::VirtualUnwindCallFrame(PREGDISPLAY pRD, EECodeInfo* pCodeInfo /*= NULL*/)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(GetControlPC(pRD) == GetIP(pRD->pCurrentContext));
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ if (pRD->IsCallerContextValid)
+ {
+ // We already have the caller's frame context
+ // We just switch the pointers
+ PT_CONTEXT temp = pRD->pCurrentContext;
+ pRD->pCurrentContext = pRD->pCallerContext;
+ pRD->pCallerContext = temp;
+
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+ PT_KNONVOLATILE_CONTEXT_POINTERS tempPtrs = pRD->pCurrentContextPointers;
+ pRD->pCurrentContextPointers = pRD->pCallerContextPointers;
+ pRD->pCallerContextPointers = tempPtrs;
+#endif // defined(_TARGET_AMD64_) || defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+ }
+ else
+ {
+ PT_KNONVOLATILE_CONTEXT_POINTERS pCurrentContextPointers = NULL;
+ NOT_X86(pCurrentContextPointers = pRD->pCurrentContextPointers);
+ VirtualUnwindCallFrame(pRD->pCurrentContext, pCurrentContextPointers, pCodeInfo);
+ }
+
+ SyncRegDisplayToCurrentContext(pRD);
+ pRD->IsCallerContextValid = FALSE;
+ pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary.
+
+ return pRD->ControlPC;
+}
+
+
+// static
+PCODE Thread::VirtualUnwindCallFrame(T_CONTEXT* pContext,
+ T_KNONVOLATILE_CONTEXT_POINTERS* pContextPointers /*= NULL*/,
+ EECodeInfo * pCodeInfo /*= NULL*/)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pContext, NULL_NOT_OK));
+ PRECONDITION(CheckPointer(pContextPointers, NULL_OK));
+ SO_TOLERANT;
+ SUPPORTS_DAC;
+ }
+ CONTRACTL_END;
+
+ PCODE uControlPc = GetIP(pContext);
+
+#if !defined(DACCESS_COMPILE)
+ UINT_PTR uImageBase;
+ PT_RUNTIME_FUNCTION pFunctionEntry;
+
+ if (pCodeInfo == NULL)
+ {
+#ifndef FEATURE_PAL
+ pFunctionEntry = RtlLookupFunctionEntry(uControlPc,
+ ARM_ONLY((DWORD*))(&uImageBase),
+ NULL);
+#else // !FEATURE_PAL
+ EECodeInfo codeInfo;
+
+ codeInfo.Init(uControlPc);
+ pFunctionEntry = codeInfo.GetFunctionEntry();
+ uImageBase = (UINT_PTR)codeInfo.GetModuleBase();
+#endif // !FEATURE_PAL
+ }
+ else
+ {
+ pFunctionEntry = pCodeInfo->GetFunctionEntry();
+ uImageBase = (UINT_PTR)pCodeInfo->GetModuleBase();
+
+ // RUNTIME_FUNCTION of cold code just points to the RUNTIME_FUNCTION of hot code. The unwinder
+ // expects this indirection to be resolved, so we use RUNTIME_FUNCTION of the hot code even
+ // if we are in cold code.
+
+#if defined(_DEBUG) && !defined(FEATURE_PAL)
+ UINT_PTR uImageBaseFromOS;
+ PT_RUNTIME_FUNCTION pFunctionEntryFromOS;
+
+ pFunctionEntryFromOS = RtlLookupFunctionEntry(uControlPc,
+ ARM_ONLY((DWORD*))(&uImageBaseFromOS),
+ NULL);
+ _ASSERTE( (uImageBase == uImageBaseFromOS) && (pFunctionEntry == pFunctionEntryFromOS) );
+#endif // _DEBUG && !FEATURE_PAL
+ }
+
+ if (pFunctionEntry)
+ {
+ uControlPc = VirtualUnwindNonLeafCallFrame(pContext, pContextPointers, pFunctionEntry, uImageBase);
+ }
+ else
+ {
+ uControlPc = VirtualUnwindLeafCallFrame(pContext);
+ }
+#else // DACCESS_COMPILE
+ // We can't use RtlVirtualUnwind() from out-of-process. Instead, we call code:DacUnwindStackFrame,
+ // which is similar to StackWalk64().
+ if (DacUnwindStackFrame(pContext, pContextPointers) == TRUE)
+ {
+ uControlPc = GetIP(pContext);
+ }
+ else
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+#endif // !DACCESS_COMPILE
+
+ return uControlPc;
+}
+
+#ifdef DACCESS_COMPILE
+
+PCODE Thread::VirtualUnwindLeafCallFrame(T_CONTEXT* pContext)
+{
+ DacNotImpl();
+ return 0;
+}
+UINT_PTR Thread::VirtualUnwindToFirstManagedCallFrame(T_CONTEXT* pContext)
+{
+ DacNotImpl();
+ return 0;
+}
+
+#else // !DACCESS_COMPILE
+
+// static
+PCODE Thread::VirtualUnwindLeafCallFrame(T_CONTEXT* pContext)
+{
+ PCODE uControlPc;
+
+#if defined(_DEBUG) && !defined(FEATURE_PAL)
+ UINT_PTR uImageBase;
+
+ PT_RUNTIME_FUNCTION pFunctionEntry = RtlLookupFunctionEntry((UINT_PTR)GetIP(pContext),
+ ARM_ONLY((DWORD*))(&uImageBase),
+ NULL);
+
+ CONSISTENCY_CHECK(NULL == pFunctionEntry);
+#endif // _DEBUG && !FEATURE_PAL
+
+#if defined(_TARGET_AMD64_)
+
+ uControlPc = *(ULONGLONG*)pContext->Rsp;
+ pContext->Rsp += sizeof(ULONGLONG);
+
+#elif defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+
+ uControlPc = TADDR(pContext->Lr);
+
+#else
+ PORTABILITY_ASSERT("Thread::VirtualUnwindLeafCallFrame");
+ uControlPc = NULL;
+#endif
+
+ SetIP(pContext, uControlPc);
+
+
+ return uControlPc;
+}
+
+// static
+PCODE Thread::VirtualUnwindNonLeafCallFrame(T_CONTEXT* pContext, KNONVOLATILE_CONTEXT_POINTERS* pContextPointers,
+ PT_RUNTIME_FUNCTION pFunctionEntry, UINT_PTR uImageBase)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pContext, NULL_NOT_OK));
+ PRECONDITION(CheckPointer(pContextPointers, NULL_OK));
+ PRECONDITION(CheckPointer(pFunctionEntry, NULL_OK));
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ PCODE uControlPc = GetIP(pContext);
+#if defined(_WIN64)
+ UINT64 EstablisherFrame;
+ PVOID HandlerData;
+#elif defined(_TARGET_ARM_)
+ DWORD EstablisherFrame;
+ PVOID HandlerData;
+#else
+ _ASSERTE(!"nyi platform stackwalking");
+#endif
+
+ if (NULL == pFunctionEntry)
+ {
+#ifndef FEATURE_PAL
+ pFunctionEntry = RtlLookupFunctionEntry(uControlPc,
+ ARM_ONLY((DWORD*))(&uImageBase),
+ NULL);
+#endif
+ if (NULL == pFunctionEntry)
+ {
+ return NULL;
+ }
+ }
+
+ RtlVirtualUnwind(NULL,
+ uImageBase,
+ uControlPc,
+ pFunctionEntry,
+ pContext,
+ &HandlerData,
+ &EstablisherFrame,
+ pContextPointers);
+
+ uControlPc = GetIP(pContext);
+ return uControlPc;
+}
+
+// static
+UINT_PTR Thread::VirtualUnwindToFirstManagedCallFrame(T_CONTEXT* pContext)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ PCODE uControlPc = GetIP(pContext);
+
+ // unwind out of this function and out of our caller to
+ // get our caller's PSP, or our caller's caller's SP.
+ while (!ExecutionManager::IsManagedCode(uControlPc))
+ {
+#ifndef FEATURE_PAL
+ uControlPc = VirtualUnwindCallFrame(pContext);
+#else // !FEATURE_PAL
+ BOOL success = PAL_VirtualUnwind(pContext, NULL);
+ if (!success)
+ {
+ _ASSERTE(!"Thread::VirtualUnwindToFirstManagedCallFrame: PAL_VirtualUnwind failed");
+ EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
+ }
+
+ uControlPc = GetIP(pContext);
+
+ if (uControlPc == 0)
+ {
+ break;
+ }
+#endif // !FEATURE_PAL
+ }
+
+ return uControlPc;
+}
+
+#endif // DACCESS_COMPILE
+#endif // WIN64EXCEPTIONS
+
+#ifdef _DEBUG
+void Thread::DebugLogStackWalkInfo(CrawlFrame* pCF, __in_z LPCSTR pszTag, UINT32 uFramesProcessed)
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+ if (pCF->isFrameless)
+ {
+ LPCSTR pszType = "";
+
+#ifdef WIN64EXCEPTIONS
+ if (pCF->IsFunclet())
+ {
+ pszType = "[funclet]";
+ }
+ else
+#endif // WIN64EXCEPTIONS
+ if (pCF->pFunc->IsNoMetadata())
+ {
+ pszType = "[no metadata]";
+ }
+
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: [%03x] %s: FRAMELESS: PC=" FMT_ADDR " SP=" FMT_ADDR " method=%s %s\n",
+ uFramesProcessed,
+ pszTag,
+ DBG_ADDR(GetControlPC(pCF->pRD)),
+ DBG_ADDR(GetRegdisplaySP(pCF->pRD)),
+ pCF->pFunc->m_pszDebugMethodName,
+ pszType));
+ }
+ else if (pCF->isNativeMarker)
+ {
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: [%03x] %s: NATIVE : PC=" FMT_ADDR " SP=" FMT_ADDR "\n",
+ uFramesProcessed,
+ pszTag,
+ DBG_ADDR(GetControlPC(pCF->pRD)),
+ DBG_ADDR(GetRegdisplaySP(pCF->pRD))));
+ }
+ else if (pCF->isNoFrameTransition)
+ {
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: [%03x] %s: NO_FRAME : PC=" FMT_ADDR " SP=" FMT_ADDR "\n",
+ uFramesProcessed,
+ pszTag,
+ DBG_ADDR(GetControlPC(pCF->pRD)),
+ DBG_ADDR(GetRegdisplaySP(pCF->pRD))));
+ }
+ else
+ {
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: [%03x] %s: EXPLICIT : PC=" FMT_ADDR " SP=" FMT_ADDR " Frame=" FMT_ADDR" vtbl=" FMT_ADDR "\n",
+ uFramesProcessed,
+ pszTag,
+ DBG_ADDR(GetControlPC(pCF->pRD)),
+ DBG_ADDR(GetRegdisplaySP(pCF->pRD)),
+ DBG_ADDR(pCF->pFrame),
+ DBG_ADDR((pCF->pFrame != FRAME_TOP) ? pCF->pFrame->GetVTablePtr() : NULL)));
+ }
+}
+#endif // _DEBUG
+
+StackWalkAction Thread::MakeStackwalkerCallback(
+ CrawlFrame* pCF,
+ PSTACKWALKFRAMESCALLBACK pCallback,
+ VOID* pData
+ DEBUG_ARG(UINT32 uFramesProcessed))
+{
+ INDEBUG(DebugLogStackWalkInfo(pCF, "CALLBACK", uFramesProcessed));
+
+ // Since we may be asynchronously walking another thread's stack,
+ // check (frequently) for stack-buffer-overrun corruptions
+ pCF->CheckGSCookies();
+
+ // Since the stackwalker callback may execute arbitrary managed code and possibly
+ // not even return (in the case of exception unwinding), explicitly clear the
+ // stackwalker thread state indicator around the callback.
+
+ CLEAR_THREAD_TYPE_STACKWALKER();
+
+ StackWalkAction swa = pCallback(pCF, (VOID*)pData);
+
+ SET_THREAD_TYPE_STACKWALKER(this);
+
+ pCF->CheckGSCookies();
+
+#ifdef _DEBUG
+ if (swa == SWA_ABORT)
+ {
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: SWA_ABORT: callback aborted the stackwalk\n"));
+ }
+#endif // _DEBUG
+
+ return swa;
+}
+
+
+#if !defined(DACCESS_COMPILE) && defined(_TARGET_X86_)
+#define STACKWALKER_MAY_POP_FRAMES
+#endif
+
+
+StackWalkAction Thread::StackWalkFramesEx(
+ PREGDISPLAY pRD, // virtual register set at crawl start
+ PSTACKWALKFRAMESCALLBACK pCallback,
+ VOID *pData,
+ unsigned flags,
+ PTR_Frame pStartFrame
+ )
+{
+ // Note: there are cases (i.e., exception handling) where we may never return from this function. This means
+ // that any C++ destructors pushed in this function will never execute, and it means that this function can
+ // never have a dynamic contract.
+ STATIC_CONTRACT_WRAPPER;
+ STATIC_CONTRACT_SO_INTOLERANT;
+ SCAN_IGNORE_THROW; // see contract above
+ SCAN_IGNORE_TRIGGER; // see contract above
+
+ _ASSERTE(pRD);
+ _ASSERTE(pCallback);
+
+ // when POPFRAMES we don't want to allow GC trigger.
+ // The only method that guarantees this now is COMPlusUnwindCallback
+#ifdef STACKWALKER_MAY_POP_FRAMES
+ ASSERT(!(flags & POPFRAMES) || pCallback == (PSTACKWALKFRAMESCALLBACK) COMPlusUnwindCallback);
+ ASSERT(!(flags & POPFRAMES) || pRD->pContextForUnwind != NULL);
+ ASSERT(!(flags & POPFRAMES) || (this == GetThread() && PreemptiveGCDisabled()));
+#else // STACKWALKER_MAY_POP_FRAMES
+ ASSERT(!(flags & POPFRAMES));
+#endif // STACKWALKER_MAY_POP_FRAMES
+
+ // We haven't set the stackwalker thread type flag yet, so it shouldn't be set. Only
+ // exception to this is if the current call is made by a hijacking profiler which
+ // redirected this thread while it was previously in the middle of another stack walk
+#ifdef PROFILING_SUPPORTED
+ _ASSERTE(CORProfilerStackSnapshotEnabled() || !IsStackWalkerThread());
+#else
+ _ASSERTE(!IsStackWalkerThread());
+#endif
+
+ StackWalkAction retVal = SWA_FAILED;
+
+ {
+ // SCOPE: Remember that we're walking the stack.
+ //
+ // Normally, we'd use a holder (ClrFlsThreadTypeSwitch) to temporarily set this
+ // flag in the thread state, but we can't in this function, since C++ destructors
+ // are forbidden when this is called for exception handling (which causes
+ // MakeStackwalkerCallback() not to return). Note that in exception handling
+ // cases, we will have already cleared the stack walker thread state indicator inside
+ // MakeStackwalkerCallback(), so we will be properly cleaned up.
+#if !defined(DACCESS_COMPILE)
+ PVOID pStackWalkThreadOrig = ClrFlsGetValue(TlsIdx_StackWalkerWalkingThread);
+#endif
+ SET_THREAD_TYPE_STACKWALKER(this);
+
+ StackFrameIterator iter;
+ if (iter.Init(this, pStartFrame, pRD, flags) == TRUE)
+ {
+ while (iter.IsValid())
+ {
+ retVal = MakeStackwalkerCallback(&iter.m_crawl, pCallback, pData DEBUG_ARG(iter.m_uFramesProcessed));
+ if (retVal == SWA_ABORT)
+ {
+ break;
+ }
+
+ retVal = iter.Next();
+ if (retVal == SWA_FAILED)
+ {
+ break;
+ }
+ }
+ }
+
+ SET_THREAD_TYPE_STACKWALKER(pStackWalkThreadOrig);
+ }
+
+ return retVal;
+} // StackWalkAction Thread::StackWalkFramesEx()
+
+StackWalkAction Thread::StackWalkFrames(PSTACKWALKFRAMESCALLBACK pCallback,
+ VOID *pData,
+ unsigned flags,
+ PTR_Frame pStartFrame)
+{
+ // Note: there are cases (i.e., exception handling) where we may never return from this function. This means
+ // that any C++ destructors pushed in this function will never execute, and it means that this function can
+ // never have a dynamic contract.
+ STATIC_CONTRACT_WRAPPER;
+ _ASSERTE((flags & THREAD_IS_SUSPENDED) == 0 || (flags & ALLOW_ASYNC_STACK_WALK));
+
+ T_CONTEXT ctx;
+ REGDISPLAY rd;
+ bool fUseInitRegDisplay;
+
+#ifndef DACCESS_COMPILE
+ _ASSERTE(GetThread() == this || (flags & ALLOW_ASYNC_STACK_WALK));
+ BOOL fDebuggerHasInitialContext = (GetFilterContext() != NULL);
+ BOOL fProfilerHasInitialContext = (GetProfilerFilterContext() != NULL);
+
+ // If this walk is seeded by a profiler, then the walk better be done by the profiler
+ _ASSERTE(!fProfilerHasInitialContext || (flags & PROFILER_DO_STACK_SNAPSHOT));
+
+ fUseInitRegDisplay = fDebuggerHasInitialContext || fProfilerHasInitialContext;
+#else
+ fUseInitRegDisplay = true;
+#endif
+
+ if(fUseInitRegDisplay)
+ {
+ if (GetProfilerFilterContext() != NULL)
+ {
+ if (!InitRegDisplay(&rd, GetProfilerFilterContext(), TRUE))
+ {
+ LOG((LF_CORPROF, LL_INFO100, "**PROF: InitRegDisplay(&rd, GetProfilerFilterContext() failure leads to SWA_FAILED.\n"));
+ return SWA_FAILED;
+ }
+ }
+ else
+ {
+ if (!InitRegDisplay(&rd, &ctx, FALSE))
+ {
+ LOG((LF_CORPROF, LL_INFO100, "**PROF: InitRegDisplay(&rd, &ctx, FALSE) failure leads to SWA_FAILED.\n"));
+ return SWA_FAILED;
+ }
+ }
+ }
+ else
+ {
+ // Initialize the context
+ memset(&ctx, 0x00, sizeof(T_CONTEXT));
+ SetIP(&ctx, 0);
+ SetSP(&ctx, 0);
+ SetFP(&ctx, 0);
+ LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK starting with partial context\n"));
+ FillRegDisplay(&rd, &ctx);
+ }
+
+#ifdef STACKWALKER_MAY_POP_FRAMES
+ if (flags & POPFRAMES)
+ rd.pContextForUnwind = &ctx;
+#endif
+
+ return StackWalkFramesEx(&rd, pCallback, pData, flags, pStartFrame);
+}
+
+StackWalkAction StackWalkFunctions(Thread * thread,
+ PSTACKWALKFRAMESCALLBACK pCallback,
+ VOID * pData)
+{
+ // Note: there are cases (i.e., exception handling) where we may never return from this function. This means
+ // that any C++ destructors pushed in this function will never execute, and it means that this function can
+ // never have a dynamic contract.
+ STATIC_CONTRACT_WRAPPER;
+
+ return thread->StackWalkFrames(pCallback, pData, FUNCTIONSONLY);
+}
+
+// ----------------------------------------------------------------------------
+// StackFrameIterator::StackFrameIterator
+//
+// Description:
+// This constructor is for the usage pattern of creating an uninitialized StackFrameIterator and then
+// calling Init() on it.
+//
+// Assumptions:
+// * The caller needs to call Init() with the correct arguments before using the StackFrameIterator.
+//
+
+StackFrameIterator::StackFrameIterator()
+{
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+ CommonCtor(NULL, NULL, 0xbaadf00d);
+} // StackFrameIterator::StackFrameIterator()
+
+// ----------------------------------------------------------------------------
+// StackFrameIterator::StackFrameIterator
+//
+// Description:
+// This constructor is for the usage pattern of creating an initialized StackFrameIterator and then
+// calling ResetRegDisp() on it.
+//
+// Arguments:
+// * pThread - the thread to walk
+// * pFrame - the starting explicit frame; NULL means use the top explicit frame from the frame chain
+// * flags - the stackwalk flags
+//
+// Assumptions:
+// * The caller can call ResetRegDisp() to use the StackFrameIterator without calling Init() first.
+//
+
+StackFrameIterator::StackFrameIterator(Thread * pThread, PTR_Frame pFrame, ULONG32 flags)
+{
+ SUPPORTS_DAC;
+ CommonCtor(pThread, pFrame, flags);
+} // StackFrameIterator::StackFrameIterator()
+
+// ----------------------------------------------------------------------------
+// StackFrameIterator::CommonCtor
+//
+// Description:
+// This is a helper for the two constructors.
+//
+// Arguments:
+// * pThread - the thread to walk
+// * pFrame - the starting explicit frame; NULL means use the top explicit frame from the frame chain
+// * flags - the stackwalk flags
+//
+
+void StackFrameIterator::CommonCtor(Thread * pThread, PTR_Frame pFrame, ULONG32 flags)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ INDEBUG(m_uFramesProcessed = 0);
+
+ m_frameState = SFITER_UNINITIALIZED;
+ m_pThread = pThread;
+
+ m_pStartFrame = pFrame;
+#if defined(_DEBUG)
+ if (m_pStartFrame != NULL)
+ {
+ m_pRealStartFrame = m_pStartFrame;
+ }
+ else if (m_pThread != NULL)
+ {
+ m_pRealStartFrame = m_pThread->GetFrame();
+ }
+ else
+ {
+ m_pRealStartFrame = NULL;
+ }
+#endif // _DEBUG
+
+ m_flags = flags;
+ m_codeManFlags = (ICodeManagerFlags)0;
+
+ m_pCachedGSCookie = NULL;
+
+#if defined(WIN64EXCEPTIONS)
+ m_sfParent = StackFrame();
+ ResetGCRefReportingState();
+ m_fDidFuncletReportGCReferences = true;
+#endif // WIN64EXCEPTIONS
+
+#if !defined(_TARGET_X86_)
+ m_pvResumableFrameTargetSP = NULL;
+#endif
+} // StackFrameIterator::CommonCtor()
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize the iterator. Note that the iterator has thread-affinity,
+// and the stackwalk flags cannot be changed once the iterator is created.
+// Depending on the flags, initialization may involve unwinding to a frame of interest.
+// The unwinding could fail.
+//
+// Arguments:
+// pThread - the thread to walk
+// pFrame - the starting explicit frame; NULL means use the top explicit frame from
+// pThread->GetFrame()
+// pRegDisp - the initial REGDISPLAY
+// flags - the stackwalk flags
+//
+// Return Value:
+// Returns true if the initialization is successful. The initialization could fail because
+// we fail to unwind.
+//
+// Notes:
+// Do not do anything funky between initializing a StackFrameIterator and actually using it.
+// In particular, do not resume the thread. We only unhijack the thread once in Init().
+// Refer to StackWalkFramesEx() for the typical usage pattern.
+//
+
+BOOL StackFrameIterator::Init(Thread * pThread,
+ PTR_Frame pFrame,
+ PREGDISPLAY pRegDisp,
+ ULONG32 flags)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pRegDisp != NULL);
+
+#if !defined(DACCESS_COMPILE)
+ // When the LIGHTUNWIND flag is set, we use the stack walk cache.
+ // On x64, accesses to the stack walk cache are synchronized by
+ // a CrstStatic, which may need to call back into the host.
+ _ASSERTE(CanThisThreadCallIntoHost() || (flags & LIGHTUNWIND) == 0);
+#endif // DACCESS_COMPILE
+
+#if !defined(_TARGET_X86_)
+ _ASSERTE(!(flags & POPFRAMES));
+ _ASSERTE(pRegDisp->pCurrentContext);
+#endif // !_TARGET_X86_
+
+ BEGIN_FORBID_TYPELOAD();
+
+#ifdef FEATURE_HIJACK
+ // We can't crawl the stack of a thread that currently has a hijack pending
+ // (since the hijack routine won't be recognized by any code manager). So we
+ // undo any hijack, the EE will re-attempt it later.
+
+#if !defined(DACCESS_COMPILE)
+ // OOP stackwalks need to deal with hijacked threads in a special way.
+ pThread->UnhijackThread();
+#endif // !DACCESS_COMPILE
+
+#endif // FEATURE_HIJACK
+
+ // FRAME_TOP and NULL must be distinct values. This assert
+ // will fire if someone changes this.
+ static_assert_no_msg(FRAME_TOP_VALUE != NULL);
+
+ m_frameState = SFITER_UNINITIALIZED;
+
+ m_pThread = pThread;
+ m_flags = flags;
+
+ ResetCrawlFrame();
+
+ m_pStartFrame = pFrame;
+ if (m_pStartFrame)
+ {
+ m_crawl.pFrame = m_pStartFrame;
+ }
+ else
+ {
+ m_crawl.pFrame = m_pThread->GetFrame();
+ _ASSERTE(m_crawl.pFrame != NULL);
+ }
+ INDEBUG(m_pRealStartFrame = m_crawl.pFrame);
+
+ if (m_crawl.pFrame != FRAME_TOP)
+ {
+ m_crawl.SetCurGSCookie(Frame::SafeGetGSCookiePtr(m_crawl.pFrame));
+ }
+
+ m_crawl.pRD = pRegDisp;
+ m_crawl.pAppDomain = pThread->GetDomain(INDEBUG(flags & PROFILER_DO_STACK_SNAPSHOT));
+
+ m_codeManFlags = (ICodeManagerFlags)((flags & QUICKUNWIND) ? 0 : UpdateAllRegs);
+ m_scanFlag = ExecutionManager::GetScanFlags();
+
+#if defined(ELIMINATE_FEF)
+ // Walk the ExInfo chain, past any specified starting frame.
+ m_exInfoWalk.Init(&(pThread->GetExceptionState()->m_currentExInfo));
+ // false means don't reset UseExInfoForStackwalk
+ m_exInfoWalk.WalkToPosition(dac_cast<TADDR>(m_pStartFrame), false);
+#endif // ELIMINATE_FEF
+
+ //
+ // These fields are used in the iteration and will be updated on a per-frame basis:
+ //
+ // EECodeInfo m_cachedCodeInfo;
+ //
+ // GSCookie * m_pCachedGSCookie;
+ //
+ // StackFrame m_sfParent;
+ //
+ // LPVOID m_pvResumableFrameTargetSP;
+ //
+
+ // process the REGDISPLAY and stop at the first frame
+ ProcessIp(GetControlPC(m_crawl.pRD));
+ ProcessCurrentFrame();
+
+ // advance to the next frame which matches the stackwalk flags
+ StackWalkAction retVal = Filter();
+
+ END_FORBID_TYPELOAD();
+
+ return (retVal == SWA_CONTINUE);
+} // StackFrameIterator::Init()
+
+//---------------------------------------------------------------------------------------
+//
+// Reset the stackwalk iterator with the specified REGDISPLAY.
+// The caller is responsible for making sure the REGDISPLAY is valid.
+// This function is very similar to Init(), except that this function takes a REGDISPLAY
+// to seed the stackwalk. This function may also unwind depending on the flags, and the
+// unwinding may fail.
+//
+// Arguments:
+// pRegDisp - new REGDISPLAY
+// bool - whether the REGDISPLAY is for the leaf frame
+//
+// Return Value:
+// Returns true if the reset is successful. The reset could fail because
+// we fail to unwind.
+//
+// Assumptions:
+// The REGDISPLAY is valid for the thread which the iterator has affinity to.
+//
+
+BOOL StackFrameIterator::ResetRegDisp(PREGDISPLAY pRegDisp,
+ bool fIsFirst)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ // It is invalid to reset a stackwalk if we are popping frames along the way.
+ ASSERT(!(m_flags & POPFRAMES));
+
+ BEGIN_FORBID_TYPELOAD();
+
+ m_frameState = SFITER_UNINITIALIZED;
+
+ // Make sure the StackFrameIterator has been initialized properly.
+ _ASSERTE(m_pThread != NULL);
+ _ASSERTE(m_flags != 0xbaadf00d);
+
+ ResetCrawlFrame();
+
+ m_crawl.isFirst = fIsFirst;
+
+ if (m_pStartFrame)
+ {
+ m_crawl.pFrame = m_pStartFrame;
+ }
+ else
+ {
+ m_crawl.pFrame = m_pThread->GetFrame();
+ _ASSERTE(m_crawl.pFrame != NULL);
+ }
+
+ if (m_crawl.pFrame != FRAME_TOP)
+ {
+ m_crawl.SetCurGSCookie(Frame::SafeGetGSCookiePtr(m_crawl.pFrame));
+ }
+
+ m_crawl.pRD = pRegDisp;
+
+ // we initialize the appdomain to be the current domain, but this nees to be updated below
+ m_crawl.pAppDomain = m_crawl.pThread->GetDomain(INDEBUG(m_flags & PROFILER_DO_STACK_SNAPSHOT));
+
+ m_codeManFlags = (ICodeManagerFlags)((m_flags & QUICKUNWIND) ? 0 : UpdateAllRegs);
+
+ // make sure the REGDISPLAY is synchronized with the CONTEXT
+ UpdateRegDisp();
+
+ PCODE curPc = GetControlPC(pRegDisp);
+ ProcessIp(curPc);
+
+ // loop the frame chain to find the closet explicit frame which is lower than the specificed REGDISPLAY
+ // (stack grows up towards lower address)
+ if (m_crawl.pFrame != FRAME_TOP)
+ {
+ TADDR curSP = GetRegdisplaySP(m_crawl.pRD);
+
+#if !defined(_TARGET_X86_)
+ if (m_crawl.IsFrameless())
+ {
+ // On 64-bit and ARM, we stop at the explicit frames contained in a managed stack frame
+ // before the managed stack frame itself.
+ EECodeManager::EnsureCallerContextIsValid(m_crawl.pRD, NULL);
+ curSP = GetSP(m_crawl.pRD->pCallerContext);
+ }
+#endif // !_TARGET_X86_
+
+#if defined(_TARGET_X86_)
+ // special processing on x86; see below for more information
+ TADDR curEBP = GetRegdisplayFP(m_crawl.pRD);
+
+ CONTEXT tmpCtx;
+ REGDISPLAY tmpRD;
+ CopyRegDisplay(m_crawl.pRD, &tmpRD, &tmpCtx);
+#endif // _TARGET_X86_
+
+ //
+ // The basic idea is to loop the frame chain until we find an explicit frame whose address is below
+ // (close to the root) the SP in the specified REGDISPLAY. This works well on WIN64 platforms.
+ // However, on x86, in M2U transitions, the Windows debuggers will pass us an incorrect REGDISPLAY
+ // for the managed stack frame at the M2U boundary. The REGDISPLAY is obtained by unwinding the
+ // marshaling stub, and it contains an SP which is actually higher (closer to the leaf) than the
+ // address of the transition frame. It is as if the explicit frame is not contained in the stack
+ // frame of any method. Here's an example:
+ //
+ // ChildEBP
+ // 0012e884 ntdll32!DbgBreakPoint
+ // 0012e89c CLRStub[StubLinkStub]@1f0ac1e
+ // 0012e8a4 invalid ESP of Foo() according to the REGDISPLAY specified by the debuggers
+ // 0012e8b4 address of transition frame (NDirectMethodFrameStandalone)
+ // 0012e8c8 real ESP of Foo() according to the transition frame
+ // 0012e8d8 managed!Dummy.Foo()+0x20
+ //
+ // The original implementation of ResetRegDisp() compares the return address of the transition frame
+ // and the IP in the specified REGDISPLAY to work around this problem. However, even this comparison
+ // is not enough because we may have recursive pinvoke calls on the stack (albeit an unlikely
+ // scenario). So in addition to the IP comparison, we also check EBP. Note that this does not
+ // require managed stack frames to be EBP-framed.
+ //
+
+ while (m_crawl.pFrame != FRAME_TOP)
+ {
+ // this check is sufficient on WIN64
+ if (dac_cast<TADDR>(m_crawl.pFrame) >= curSP)
+ {
+#if defined(_TARGET_X86_)
+ // check the IP
+ if (m_crawl.pFrame->GetReturnAddress() != curPc)
+ {
+ break;
+ }
+ else
+ {
+ // unwind the REGDISPLAY using the transition frame and check the EBP
+ m_crawl.pFrame->UpdateRegDisplay(&tmpRD);
+ if (GetRegdisplayFP(&tmpRD) != curEBP)
+ {
+ break;
+ }
+ }
+#else // !_TARGET_X86_
+ break;
+#endif // !_TARGET_X86_
+ }
+
+ // if the REGDISPLAY represents the managed stack frame at a M2U transition boundary,
+ // update the flags on the CrawlFrame and the REGDISPLAY
+ PCODE frameRetAddr = m_crawl.pFrame->GetReturnAddress();
+ if (frameRetAddr == curPc)
+ {
+ unsigned uFrameAttribs = m_crawl.pFrame->GetFrameAttribs();
+
+ m_crawl.isFirst = ((uFrameAttribs & Frame::FRAME_ATTR_RESUMABLE) != 0);
+ m_crawl.isInterrupted = ((uFrameAttribs & Frame::FRAME_ATTR_EXCEPTION) != 0);
+
+ if (m_crawl.isInterrupted)
+ {
+ m_crawl.hasFaulted = ((uFrameAttribs & Frame::FRAME_ATTR_FAULTED) != 0);
+ m_crawl.isIPadjusted = ((uFrameAttribs & Frame::FRAME_ATTR_OUT_OF_LINE) != 0);
+ }
+
+ m_crawl.pFrame->UpdateRegDisplay(m_crawl.pRD);
+
+ _ASSERTE(curPc == GetControlPC(m_crawl.pRD));
+ }
+
+ // this call also updates the appdomain if the explicit frame is a ContextTransitionFrame
+ m_crawl.GotoNextFrame();
+ }
+ }
+
+#if defined(ELIMINATE_FEF)
+ // Similarly, we need to walk the ExInfos.
+ m_exInfoWalk.Init(&(m_crawl.pThread->GetExceptionState()->m_currentExInfo));
+ // false means don't reset UseExInfoForStackwalk
+ m_exInfoWalk.WalkToPosition(GetRegdisplaySP(m_crawl.pRD), false);
+#endif // ELIMINATE_FEF
+
+ // now that everything is at where it should be, update the CrawlFrame
+ ProcessCurrentFrame();
+
+ // advance to the next frame which matches the stackwalk flags
+ StackWalkAction retVal = Filter();
+
+ END_FORBID_TYPELOAD();
+
+ return (retVal == SWA_CONTINUE);
+} // StackFrameIterator::ResetRegDisp()
+
+
+//---------------------------------------------------------------------------------------
+//
+// Reset the CrawlFrame owned by the iterator. Used by both Init() and ResetRegDisp().
+//
+// Assumptions:
+// this->m_pThread and this->m_flags have been initialized.
+//
+// Notes:
+// In addition, the following fields are not reset. The caller must update them:
+// pFrame, pFunc, pAppDomain, pRD
+//
+// Fields updated by ProcessIp():
+// isFrameless, and codeInfo
+//
+// Fields updated by ProcessCurrentFrame():
+// codeManState
+//
+
+void StackFrameIterator::ResetCrawlFrame()
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ INDEBUG(memset(&(m_crawl.pFunc), 0xCC, sizeof(m_crawl.pFunc)));
+
+ m_crawl.isFirst = true;
+ m_crawl.isInterrupted = false;
+ m_crawl.hasFaulted = false;
+ m_crawl.isIPadjusted = false; // can be removed
+
+ m_crawl.isNativeMarker = false;
+ m_crawl.isProfilerDoStackSnapshot = !!(this->m_flags & PROFILER_DO_STACK_SNAPSHOT);
+ m_crawl.isNoFrameTransition = false;
+
+ m_crawl.taNoFrameTransitionMarker = NULL;
+
+#if defined(WIN64EXCEPTIONS)
+ m_crawl.isFilterFunclet = false;
+ m_crawl.isFilterFuncletCached = false;
+ m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
+ m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = false;
+#endif // WIN64EXCEPTIONS
+
+ m_crawl.pThread = this->m_pThread;
+
+ m_crawl.pSecurityObject = NULL;
+ m_crawl.isCachedMethod = false;
+ m_crawl.stackWalkCache.ClearEntry();
+
+ m_crawl.pCurGSCookie = NULL;
+ m_crawl.pFirstGSCookie = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function represents whether the iterator has reached the root of the stack or not.
+// It can be used as the loop-terminating condition for the iterator.
+//
+// Return Value:
+// Returns true if there is more frames on the stack to walk.
+//
+
+BOOL StackFrameIterator::IsValid(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ // There is more to iterate if the stackwalk is currently in managed code,
+ // or if there are frames left.
+ // If there is an ExInfo with a pContext, it may substitute for a Frame,
+ // if the ExInfo is due to an exception in managed code.
+ if (!m_crawl.isFrameless && m_crawl.pFrame == FRAME_TOP)
+ {
+ // if we are stopped at a native marker frame, we can still advance at least once more
+ if (m_frameState == SFITER_NATIVE_MARKER_FRAME)
+ {
+ _ASSERTE(m_crawl.isNativeMarker);
+ return TRUE;
+ }
+
+#if defined(ELIMINATE_FEF)
+ // Not in managed code, and no frames left -- check for an ExInfo.
+ // @todo: check for exception?
+ m_exInfoWalk.WalkToManaged();
+ if (m_exInfoWalk.GetContext())
+ return TRUE;
+#endif // ELIMINATE_FEF
+
+#ifdef _DEBUG
+ // Try to ensure that the frame chain did not change underneath us.
+ // In particular, is thread's starting frame the same as it was when
+ // we started?
+ //DevDiv 168789: In GCStress >= 4 two threads could race on triggering GC;
+ // if the one that just made p/invoke call is second and hits the trap instruction
+ // before call to syncronize with GC, it will push a frame [ResumableFrame on Unix
+ // and RedirectedThreadFrame on Windows] concurrently with GC stackwalking.
+ // In normal case (no GCStress), after p/invoke, IL_STUB will check if GC is in progress and syncronize.
+ BOOL bRedirectedPinvoke = FALSE;
+
+#ifdef FEATURE_HIJACK
+ bRedirectedPinvoke = ((GCStress<cfg_instr>::IsEnabled()) &&
+ (m_pRealStartFrame != NULL) &&
+ (m_pRealStartFrame != FRAME_TOP) &&
+ (m_pRealStartFrame->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr()) &&
+ (m_pThread->GetFrame() != NULL) &&
+ (m_pThread->GetFrame() != FRAME_TOP) &&
+ ((m_pThread->GetFrame()->GetVTablePtr() == ResumableFrame::GetMethodFrameVPtr()) ||
+ (m_pThread->GetFrame()->GetVTablePtr() == RedirectedThreadFrame::GetMethodFrameVPtr())));
+#endif // FEATURE_HIJACK
+
+ _ASSERTE( (m_pStartFrame != NULL) ||
+ (m_flags & POPFRAMES) ||
+ (m_pRealStartFrame == m_pThread->GetFrame()) ||
+ (bRedirectedPinvoke));
+#endif //_DEBUG
+
+ return FALSE;
+ }
+
+ return TRUE;
+} // StackFrameIterator::IsValid()
+
+//---------------------------------------------------------------------------------------
+//
+// Advance to the next frame according to the stackwalk flags. If the iterator is stopped
+// at some place not specified by the stackwalk flags, this function will automatically advance
+// to the next frame.
+//
+// Return Value:
+// SWA_CONTINUE (== SWA_DONE) if the iterator is successful in advancing to the next frame
+// SWA_FAILED if an operation performed by the iterator fails
+//
+// Notes:
+// This function returns SWA_DONE when advancing from the last frame to becoming invalid.
+// It returns SWA_FAILED if the iterator is invalid.
+//
+
+StackWalkAction StackFrameIterator::Next(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ if (!IsValid())
+ {
+ return SWA_FAILED;
+ }
+
+ BEGIN_FORBID_TYPELOAD();
+
+ StackWalkAction retVal = NextRaw();
+ if (retVal == SWA_CONTINUE)
+ {
+ retVal = Filter();
+ }
+
+ END_FORBID_TYPELOAD();
+ return retVal;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Check whether we should stop at the current frame given the stackwalk flags.
+// If not, continue advancing to the next frame.
+//
+// Return Value:
+// Returns SWA_CONTINUE (== SWA_DONE) if the iterator is invalid or if no automatic advancing is done.
+// Otherwise returns whatever the last call to NextRaw() returns.
+//
+
+StackWalkAction StackFrameIterator::Filter(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ bool fStop = false;
+ bool fSkippingFunclet = false;
+
+#if defined(WIN64EXCEPTIONS)
+ bool fRecheckCurrentFrame = false;
+ bool fSkipFuncletCallback = true;
+#endif // defined(WIN64EXCEPTIONS)
+
+ StackWalkAction retVal = SWA_CONTINUE;
+
+ while (IsValid())
+ {
+ fStop = false;
+ fSkippingFunclet = false;
+
+#if defined(WIN64EXCEPTIONS)
+ ExceptionTracker* pTracker = m_crawl.pThread->GetExceptionState()->GetCurrentExceptionTracker();
+ fRecheckCurrentFrame = false;
+ fSkipFuncletCallback = true;
+
+ // by default, there is no funclet for the current frame
+ // that reported GC references
+ m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
+
+ // By default, assume that we are going to report GC references for this
+ // CrawlFrame
+ m_crawl.fShouldCrawlframeReportGCReferences = true;
+
+ // By default, assume that parent frame is going to report GC references from
+ // the actual location reported by the stack walk.
+ m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = false;
+
+ if (!m_sfParent.IsNull())
+ {
+ // we are now skipping frames to get to the funclet's parent
+ fSkippingFunclet = true;
+ }
+#endif // WIN64EXCEPTIONS
+
+ switch (m_frameState)
+ {
+ case SFITER_FRAMELESS_METHOD:
+#if defined(WIN64EXCEPTIONS)
+ProcessFuncletsForGCReporting:
+ do
+ {
+ // When enumerating GC references for "liveness" reporting, depending upon the architecture,
+ // the responsibility of who reports what varies:
+ //
+ // 1) On ARM, ARM64, and X64 (using RyuJIT), the funclet reports all references belonging
+ // to itself and its parent method. This is indicated by the WantsReportOnlyLeaf flag being
+ // set in the GC information for a function.
+ //
+ // 2) X64 (using JIT64) has the reporting distributed between the funclets and the parent method.
+ // If some reference(s) get double reported, JIT64 can handle that by playing conservative.
+ // JIT64 does NOT set the WantsReportOnlyLeaf flag in the function GC information.
+ //
+ // 3) On ARM, the reporting is done by funclets (if present). Otherwise, the primary method
+ // does it.
+ //
+ // 4) x86 behaves like (1)
+ //
+ // For non-x86, the GcStackCrawlCallBack is invoked with a new flag indicating that
+ // the stackwalk is being done for GC reporting purposes - this flag is GC_FUNCLET_REFERENCE_REPORTING.
+ // The presence of this flag influences how the stackwalker will enumerate frames; which frames will
+ // result in the callback being invoked; etc. The idea is that we want to report only the
+ // relevant frames via the callback that are active on the callstack. This removes the need to
+ // double report (even though JIT64 does it), reporting of dead frames, and makes the
+ // design of reference reporting more consistent (and easier to understand) across architectures.
+ //
+ // The algorithm is as follows (at a conceptual level):
+ //
+ // 1) For each enumerated managed (frameless) frame, check if it is a funclet or not.
+ // 1.1) If it is not a funclet, pass the frame to the callback and goto (2).
+ // 1.2) If it is a funclet, we preserve the callerSP of the parent frame where the funclet was invoked from.
+ // Pass the funclet to the callback.
+ // 1.3) For filter funclets, we enumerate all frames until we reach the parent. Once the parent is reached,
+ // pass it to the callback with a flag indicating that its corresponding funclet has already performed
+ // the reporting.
+ // 1.4) For non-filter funclets, we skip all the frames until we reach the parent. Once the parent is reached,
+ // pass it to the callback with a flag indicating that its corresponding funclet has already performed
+ // the reporting.
+ // 1.5) If we see non-filter funclets while processing a filter funclet, then goto (1.4). Once we have reached the
+ // parent of the non-filter funclet, resume filter funclet processing as described in (1.3).
+ // 2) If another frame is enumerated, goto (1). Otherwise, stackwalk is complete.
+ //
+ // Note: When a flag is passed to the callback indicating that the funclet for a parent frame has already
+ // reported the references, RyuJIT will simply do nothing and return from the callback.
+ // JIT64, on the other hand, will ignore the flag and perform reporting (again).
+ //
+ // Note: For non-filter funclets there is a small window during unwind where we have conceptually unwound past a
+ // funclet but have not yet reached the parent/handling frame. In this case we might need the parent to
+ // report its GC roots. See comments around use of m_fDidFuncletReportGCReferences for more details.
+ //
+ // Needless to say, all applicable (read: active) explicit frames are also processed.
+
+ // Check if we are in the mode of enumerating GC references (or not)
+ if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ {
+#ifdef FEATURE_PAL
+ // For interleaved exception handling on non-windows systems, we need to find out if the current frame
+ // was a caller of an already executed exception handler based on the previous exception trackers.
+ // The handler funclet frames are already gone from the stack, so the exception trackers are the
+ // only source of evidence about it.
+ // The filter funclet is different though, its frame is always present on the stack when its parent
+ // frame is reached by the stack walker, because no exception can escape a filter funclet.
+ // This is different from Windows where the full stack is preserved until an exception is fully handled
+ // and so we can detect it just from walking the stack.
+ bool fProcessingFilterFunclet = !m_sfFuncletParent.IsNull() && !(m_fProcessNonFilterFunclet || m_fProcessIntermediaryNonFilterFunclet);
+ if (!fRecheckCurrentFrame && !fSkippingFunclet && (pTracker != NULL) && !fProcessingFilterFunclet)
+ {
+ // The stack walker is not skipping frames now, which means it didn't find a funclet frame that
+ // would require skipping the current frame. If we find a tracker with caller of actual handling
+ // frame matching the current frame, it means that the funclet stack frame was reclaimed.
+ StackFrame sfFuncletParent;
+ ExceptionTracker* pCurrTracker = pTracker;
+ bool hasFuncletStarted = m_crawl.pThread->GetExceptionState()->GetCurrentEHClauseInfo()->IsManagedCodeEntered();
+
+ while (pCurrTracker != NULL)
+ {
+ if (hasFuncletStarted)
+ {
+ sfFuncletParent = pCurrTracker->GetCallerOfEnclosingClause();
+ if (!sfFuncletParent.IsNull() && ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, sfFuncletParent))
+ {
+ break;
+ }
+ }
+
+ sfFuncletParent = pCurrTracker->GetCallerOfCollapsedEnclosingClause();
+ if (!sfFuncletParent.IsNull() && ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, sfFuncletParent))
+ {
+ break;
+ }
+
+ // Funclets handling exception for trackers older than the current one were always started,
+ // since the current tracker was created due to an exception in the funclet belonging to
+ // the previous tracker.
+ hasFuncletStarted = true;
+ pCurrTracker = pCurrTracker->GetPreviousExceptionTracker();
+ }
+
+ if (pCurrTracker != NULL)
+ {
+ // The current frame is a parent of a funclet that was already unwound and removed from the stack
+ // Set the members the same way we would set them on Windows when we
+ // would detect this just from stack walking.
+ m_sfParent = sfFuncletParent;
+ m_sfFuncletParent = sfFuncletParent;
+ m_fProcessNonFilterFunclet = true;
+ m_fDidFuncletReportGCReferences = false;
+ fSkippingFunclet = true;
+ }
+ }
+#endif // FEATURE_PAL
+
+ fRecheckCurrentFrame = false;
+ // Do we already have a reference to a funclet parent?
+ if (!m_sfFuncletParent.IsNull())
+ {
+ // Have we been processing a filter funclet without encountering any non-filter funclets?
+ if ((m_fProcessNonFilterFunclet == false) && (m_fProcessIntermediaryNonFilterFunclet == false))
+ {
+ // Yes, we have. Check the current frame and if it is the parent we are looking for,
+ // clear the flag indicating that its funclet has already reported the GC references (see
+ // below comment for Dev11 376329 explaining why we do this).
+ if (ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, m_sfFuncletParent))
+ {
+ STRESS_LOG2(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: Reached parent of filter funclet @ CallerSP: %p, m_crawl.pFunc = %p\n",
+ m_sfFuncletParent.SP, m_crawl.pFunc);
+
+ // Dev11 376329 - ARM: GC hole during filter funclet dispatch.
+ // Filters are invoked during the first pass so we cannot skip
+ // reporting the parent frame since it's still live. Normally
+ // this would cause double reporting, however for filters the JIT
+ // will report all GC roots as pinned to alleviate this problem.
+ // Note that JIT64 does not have this problem since it always
+ // reports the parent frame (this flag is essentially ignored)
+ // so it's safe to make this change for all (non-x86) architectures.
+ m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
+ ResetGCRefReportingState();
+
+ // We have reached the parent of the filter funclet.
+ // It is possible this is another funclet (e.g. a catch/fault/finally),
+ // so reexamine this frame and see if it needs any skipping.
+ fRecheckCurrentFrame = true;
+ }
+ else
+ {
+ // When processing filter funclets, until we reach the parent frame
+ // we should be seeing only non--filter-funclet frames. This is because
+ // exceptions cannot escape filter funclets. Thus, there can be no frameless frames
+ // between the filter funclet and its parent.
+ _ASSERTE(!m_crawl.IsFilterFunclet());
+ if (m_crawl.IsFunclet())
+ {
+ // This is a non-filter funclet encountered when processing a filter funclet.
+ // In such a case, we will deliver a callback for it and skip frames until we reach
+ // its parent. Once there, we will resume frame enumeration for finding
+ // parent of the filter funclet we were originally processing.
+ m_sfIntermediaryFuncletParent = ExceptionTracker::FindParentStackFrameForStackWalk(&m_crawl, true);
+ _ASSERTE(!m_sfIntermediaryFuncletParent.IsNull());
+ m_fProcessIntermediaryNonFilterFunclet = true;
+
+ // Set the parent frame so that the funclet skipping logic (further below)
+ // can use it.
+ m_sfParent = m_sfIntermediaryFuncletParent;
+ fSkipFuncletCallback = false;
+ }
+ }
+ }
+ }
+ else
+ {
+ _ASSERTE(m_sfFuncletParent.IsNull());
+
+ // We don't have any funclet parent reference. Check if the current frame represents a funclet.
+ if (m_crawl.IsFunclet())
+ {
+ // Get a reference to the funclet's parent frame.
+ m_sfFuncletParent = ExceptionTracker::FindParentStackFrameForStackWalk(&m_crawl, true);
+
+ if (m_sfFuncletParent.IsNull())
+ {
+ // This can only happen if the funclet (and its parent) have been unwound.
+ _ASSERTE(ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl));
+ }
+ else
+ {
+ // We should have found the funclet's parent stackframe
+ _ASSERTE(!m_sfFuncletParent.IsNull());
+
+ bool fIsFilterFunclet = m_crawl.IsFilterFunclet();
+
+ STRESS_LOG4(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: Found %sFilter funclet @ SP: %p, m_crawl.pFunc = %p; FuncletParentCallerSP: %p\n",
+ (fIsFilterFunclet) ? "" : "Non-", GetRegdisplaySP(m_crawl.GetRegisterSet()), m_crawl.pFunc, m_sfFuncletParent.SP);
+
+ if (!fIsFilterFunclet)
+ {
+ m_fProcessNonFilterFunclet = true;
+
+ // Set the parent frame so that the funclet skipping logic (further below)
+ // can use it.
+ m_sfParent = m_sfFuncletParent;
+
+ // For non-filter funclets, we will make the callback for the funclet
+ // but skip all the frames until we reach the parent method. When we do,
+ // we will make a callback for it as well and then continue to make callbacks
+ // for all upstack frames, until we reach another funclet or the top of the stack
+ // is reached.
+ fSkipFuncletCallback = false;
+ }
+ else
+ {
+ _ASSERTE(fIsFilterFunclet);
+ m_fProcessNonFilterFunclet = false;
+
+ // Nothing more to do as we have come across a filter funclet. In this case, we will:
+ //
+ // 1) Get a reference to the parent frame
+ // 2) Report the funclet
+ // 3) Continue to report the parent frame, along with a flag that funclet has been reported (see above)
+ // 4) Continue to report all upstack frames
+ }
+ }
+ } // end if (m_crawl.IsFunclet())
+ }
+ } // end if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ }
+ while(fRecheckCurrentFrame == true);
+
+ if ((m_fProcessNonFilterFunclet == true) || (m_fProcessIntermediaryNonFilterFunclet == true) || (m_flags & (FUNCTIONSONLY | SKIPFUNCLETS)))
+ {
+ bool fSkipFrameDueToUnwind = false;
+
+ if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ {
+ // When a nested exception escapes, it will unwind past a funclet. In addition, it will
+ // unwind the frame chain up to the funclet. When that happens, we'll basically lose
+ // all the stack frames higher than and equal to the funclet. We can't skip funclets in
+ // the usual way because the first frame we see won't be a funclet. It will be something
+ // which has conceptually been unwound. We need to use the information on the
+ // ExceptionTracker to determine if a stack frame is in the unwound stack region.
+ //
+ // If we are enumerating frames for GC reporting and we determined that
+ // the current frame needs to be reported, ensure that it has not already
+ // been unwound by the active exception. If it has been, then we will set a flag
+ // indicating that its references need not be reported. The CrawlFrame, however,
+ // will still be passed to the GC stackwalk callback in case it represents a dynamic
+ // method, to allow the GC to keep that method alive.
+ if (ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl))
+ {
+ // Invoke the GC callback for this crawlframe (to keep any dynamic methods alive) but do not report its references.
+ m_crawl.fShouldCrawlframeReportGCReferences = false;
+ fSkipFrameDueToUnwind = true;
+
+ if (m_crawl.IsFunclet() && !fSkippingFunclet)
+ {
+ // we have come across a funclet that has been unwound and we haven't yet started to
+ // look for its parent. in such a case, the funclet will not have anything to report
+ // so set the corresponding flag to indicate so.
+
+ _ASSERTE(m_fDidFuncletReportGCReferences);
+ m_fDidFuncletReportGCReferences = false;
+
+ STRESS_LOG0(LF_GCROOTS, LL_INFO100, "Unwound funclet will skip reporting references\n");
+ }
+ }
+ }
+ else if (m_flags & (FUNCTIONSONLY | SKIPFUNCLETS))
+ {
+ if (ExceptionTracker::IsInStackRegionUnwoundByCurrentException(&m_crawl))
+ {
+ // don't stop here
+ fSkipFrameDueToUnwind = true;
+ }
+ }
+
+ if (fSkipFrameDueToUnwind)
+ {
+ if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ {
+ // Check if we are skipping frames.
+ if (!m_sfParent.IsNull())
+ {
+ // Check if our have reached our target method frame.
+ // IsMaxVal() is a special value to indicate that we should skip one frame.
+ if (m_sfParent.IsMaxVal() ||
+ ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, m_sfParent))
+ {
+ // Reset flag as we have reached target method frame so no more skipping required
+ fSkippingFunclet = false;
+
+ // We've finished skipping as told. Now check again.
+
+ if ((m_fProcessIntermediaryNonFilterFunclet == true) || (m_fProcessNonFilterFunclet == true))
+ {
+ STRESS_LOG2(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: Reached parent of non-filter funclet @ CallerSP: %p, m_crawl.pFunc = %p\n",
+ m_sfParent.SP, m_crawl.pFunc);
+
+ // landing here indicates that the funclet's parent has been unwound so
+ // this will always be true, no need to predicate on the state of the funclet
+ m_crawl.fShouldParentToFuncletSkipReportingGCReferences = true;
+
+ // we've reached the parent so reset our state
+ m_fDidFuncletReportGCReferences = true;
+
+ ResetGCRefReportingState(m_fProcessIntermediaryNonFilterFunclet);
+ }
+
+ m_sfParent.Clear();
+
+ if (m_crawl.IsFunclet())
+ {
+ // We've hit a funclet.
+ // Since we are in GC reference reporting mode,
+ // then avoid code duplication and go to
+ // funclet processing.
+ fRecheckCurrentFrame = true;
+ goto ProcessFuncletsForGCReporting;
+ }
+ }
+ }
+ } // end if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+
+ if (m_crawl.fShouldCrawlframeReportGCReferences)
+ {
+ // Skip the callback for this frame - we don't do this for unwound frames encountered
+ // in GC stackwalk since they may represent dynamic methods whose resolver objects
+ // the GC may need to keep alive.
+ break;
+ }
+ }
+ else
+ {
+ _ASSERTE(!fSkipFrameDueToUnwind);
+
+ // Check if we are skipping frames.
+ if (!m_sfParent.IsNull())
+ {
+ // Check if we have reached our target method frame.
+ // IsMaxVal() is a special value to indicate that we should skip one frame.
+ if (m_sfParent.IsMaxVal() ||
+ ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, m_sfParent))
+ {
+ // We've finished skipping as told. Now check again.
+ if ((m_fProcessIntermediaryNonFilterFunclet == true) || (m_fProcessNonFilterFunclet == true))
+ {
+ // If we are here, we should be in GC reference reporting mode.
+ _ASSERTE(m_flags & GC_FUNCLET_REFERENCE_REPORTING);
+
+ STRESS_LOG2(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: Reached parent of non-filter funclet @ CallerSP: %p, m_crawl.pFunc = %p\n",
+ m_sfParent.SP, m_crawl.pFunc);
+
+ // by default a funclet's parent won't report its GC roots since they would have already
+ // been reported by the funclet. however there is a small window during unwind before
+ // control returns to the OS where we might require the parent to report. more below.
+ bool shouldSkipReporting = true;
+
+ if (!m_fDidFuncletReportGCReferences)
+ {
+ // we have reached the parent frame of the funclet which didn't report roots since it was already unwound.
+ // check if the parent frame of the funclet is also handling an exception. if it is, then we will need to
+ // report roots for it since the catch handler may use references inside it.
+
+ STRESS_LOG0(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: Reached parent of funclet which didn't report GC roots, since funclet is already unwound.\n");
+
+ if (pTracker->GetCallerOfActualHandlingFrame() == m_sfFuncletParent)
+ {
+ // we should not skip reporting for this parent frame
+ shouldSkipReporting = false;
+
+ // now that we've found the parent that will report roots reset our state.
+ m_fDidFuncletReportGCReferences = true;
+
+ // After funclet gets unwound parent will begin to report gc references. Reporting GC references
+ // using the IP of throw in parent method can crash application. Parent could have locals objects
+ // which might not have been reported by funclet as live and would have already been collected
+ // when funclet was on stack. Now if parent starts using IP of throw to report gc references it
+ // would report garbage values as live objects. So instead parent can use the IP of the resume
+ // address of catch funclet to report live GC references.
+ m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = true;
+ // Store catch clause info. Helps retrieve IP of resume address.
+ m_crawl.ehClauseForCatch = pTracker->GetEHClauseForCatch();
+
+ STRESS_LOG3(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: Parent of funclet which didn't report GC roots is handling an exception at 0x%p"
+ "(EH handler range [%x, %x) ), so we need to specially report roots to ensure variables alive"
+ " in its handler stay live.\n",
+ pTracker->GetCatchToCallPC(), m_crawl.ehClauseForCatch.HandlerStartPC,
+ m_crawl.ehClauseForCatch.HandlerEndPC);
+ }
+ else if (!m_crawl.IsFunclet())
+ {
+ // we've reached the parent and it's not handling an exception, it's also not
+ // a funclet so reset our state. note that we cannot reset the state when the
+ // parent is a funclet since the leaf funclet didn't report any references and
+ // we might have a catch handler below us that might contain GC roots.
+ m_fDidFuncletReportGCReferences = true;
+ }
+
+ STRESS_LOG4(LF_GCROOTS, LL_INFO100,
+ "Funclet didn't report references: handling frame: %p, m_sfFuncletParent = %p, is funclet: %d, skip reporting %d\n",
+ pTracker->GetEstablisherOfActualHandlingFrame().SP, m_sfFuncletParent.SP, m_crawl.IsFunclet(), shouldSkipReporting);
+ }
+ m_crawl.fShouldParentToFuncletSkipReportingGCReferences = shouldSkipReporting;
+
+ ResetGCRefReportingState(m_fProcessIntermediaryNonFilterFunclet);
+ }
+
+ m_sfParent.Clear();
+ }
+ } // end if (!m_sfParent.IsNull())
+
+ if (m_sfParent.IsNull() && m_crawl.IsFunclet())
+ {
+ // We've hit a funclet.
+ if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ {
+ // If we are in GC reference reporting mode,
+ // then avoid code duplication and go to
+ // funclet processing.
+ fRecheckCurrentFrame = true;
+ goto ProcessFuncletsForGCReporting;
+ }
+ else
+ {
+ // Start skipping frames.
+ m_sfParent = ExceptionTracker::FindParentStackFrameForStackWalk(&m_crawl);
+ }
+
+ // m_sfParent can be NULL if the current funclet is a filter,
+ // in which case we shouldn't skip the frames.
+ }
+
+ // If we're skipping frames due to a funclet on the stack
+ // or this is an IL stub (which don't get reported when
+ // FUNCTIONSONLY is set) we skip the callback.
+ //
+ // The only exception is the GC reference reporting mode -
+ // for it, we will callback for the funclet so that references
+ // are reported and then continue to skip all frames between the funclet
+ // and its parent, eventually making a callback for the parent as well.
+ if (m_flags & (FUNCTIONSONLY | SKIPFUNCLETS))
+ {
+ if (!m_sfParent.IsNull() || m_crawl.pFunc->IsILStub())
+ {
+ STRESS_LOG4(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: %s: not making callback for this frame, SPOfParent = %p, \
+ isILStub = %d, m_crawl.pFunc = %pM\n",
+ (!m_sfParent.IsNull() ? "SKIPPING_TO_FUNCLET_PARENT" : "IS_IL_STUB"),
+ m_sfParent.SP,
+ (m_crawl.pFunc->IsILStub() ? 1 : 0),
+ m_crawl.pFunc);
+
+ // don't stop here
+ break;
+ }
+ }
+ else if (fSkipFuncletCallback && (m_flags & GC_FUNCLET_REFERENCE_REPORTING))
+ {
+ if (!m_sfParent.IsNull())
+ {
+ STRESS_LOG4(LF_GCROOTS, LL_INFO100,
+ "STACKWALK: %s: not making callback for this frame, SPOfParent = %p, \
+ isILStub = %d, m_crawl.pFunc = %pM\n",
+ (!m_sfParent.IsNull() ? "SKIPPING_TO_FUNCLET_PARENT" : "IS_IL_STUB"),
+ m_sfParent.SP,
+ (m_crawl.pFunc->IsILStub() ? 1 : 0),
+ m_crawl.pFunc);
+
+ // don't stop here
+ break;
+ }
+ }
+ }
+ }
+ else if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ {
+ // If we are enumerating frames for GC reporting and we determined that
+ // the current frame needs to be reported, ensure that it has not already
+ // been unwound by the active exception. If it has been, then we will
+ // simply skip it and not deliver a callback for it.
+ if (ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl))
+ {
+ // Invoke the GC callback for this crawlframe (to keep any dynamic methods alive) but do not report its references.
+ m_crawl.fShouldCrawlframeReportGCReferences = false;
+ }
+ }
+
+#else // WIN64EXCEPTIONS
+ // Skip IL stubs
+ if (m_flags & FUNCTIONSONLY)
+ {
+ if (m_crawl.pFunc->IsILStub())
+ {
+ LOG((LF_GCROOTS, LL_INFO100000,
+ "STACKWALK: IS_IL_STUB: not making callback for this frame, m_crawl.pFunc = %s\n",
+ m_crawl.pFunc->m_pszDebugMethodName));
+
+ // don't stop here
+ break;
+ }
+ }
+#endif // WIN64EXCEPTIONS
+
+ fStop = true;
+ break;
+
+ case SFITER_FRAME_FUNCTION:
+ //
+ // fall through
+ //
+
+ case SFITER_SKIPPED_FRAME_FUNCTION:
+ if (!fSkippingFunclet)
+ {
+#if defined(WIN64EXCEPTIONS)
+ if (m_flags & GC_FUNCLET_REFERENCE_REPORTING)
+ {
+ // If we are enumerating frames for GC reporting and we determined that
+ // the current frame needs to be reported, ensure that it has not already
+ // been unwound by the active exception. If it has been, then we will
+ // simply skip it and not deliver a callback for it.
+ if (ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl))
+ {
+ // Invoke the GC callback for this crawlframe (to keep any dynamic methods alive) but do not report its references.
+ m_crawl.fShouldCrawlframeReportGCReferences = false;
+ }
+ }
+ else if (m_flags & (FUNCTIONSONLY | SKIPFUNCLETS))
+ {
+ // See the comment above for IsInStackRegionUnwoundByCurrentException().
+ if (ExceptionTracker::IsInStackRegionUnwoundByCurrentException(&m_crawl))
+ {
+ // don't stop here
+ break;
+ }
+ }
+#endif // WIN64EXCEPTIONS
+ if ( (m_crawl.pFunc != NULL) || !(m_flags & FUNCTIONSONLY) )
+ {
+ fStop = true;
+ }
+ }
+ break;
+
+ case SFITER_NO_FRAME_TRANSITION:
+ if (!fSkippingFunclet)
+ {
+ if (m_flags & NOTIFY_ON_NO_FRAME_TRANSITIONS)
+ {
+ _ASSERTE(m_crawl.isNoFrameTransition == true);
+ fStop = true;
+ }
+ }
+ break;
+
+ case SFITER_NATIVE_MARKER_FRAME:
+ if (!fSkippingFunclet)
+ {
+ if (m_flags & NOTIFY_ON_U2M_TRANSITIONS)
+ {
+ _ASSERTE(m_crawl.isNativeMarker == true);
+ fStop = true;
+ }
+ }
+ break;
+
+ case SFITER_INITIAL_NATIVE_CONTEXT:
+ if (!fSkippingFunclet)
+ {
+ if (m_flags & NOTIFY_ON_INITIAL_NATIVE_CONTEXT)
+ {
+ fStop = true;
+ }
+ }
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ if (fStop)
+ {
+ break;
+ }
+ else
+ {
+ INDEBUG(m_crawl.pThread->DebugLogStackWalkInfo(&m_crawl, "FILTER ", m_uFramesProcessed));
+ retVal = NextRaw();
+ if (retVal != SWA_CONTINUE)
+ {
+ break;
+ }
+ }
+ }
+
+ return retVal;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Advance to the next frame and stop, regardless of the stackwalk flags.
+//
+// Return Value:
+// SWA_CONTINUE (== SWA_DONE) if the iterator is successful in advancing to the next frame
+// SWA_FAILED if an operation performed by the iterator fails
+//
+// Assumptions:
+// The caller has checked that the iterator is valid.
+//
+// Notes:
+// This function returns SWA_DONE when advancing from the last frame to becoming invalid.
+//
+
+StackWalkAction StackFrameIterator::NextRaw(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ _ASSERTE(IsValid());
+
+ INDEBUG(m_uFramesProcessed++);
+
+ StackWalkAction retVal = SWA_CONTINUE;
+
+ if (m_frameState == SFITER_SKIPPED_FRAME_FUNCTION)
+ {
+#if !defined(_TARGET_X86_) && defined(_DEBUG)
+ // make sure we're not skipping a different transition
+ if (m_crawl.pFrame->NeedsUpdateRegDisplay())
+ {
+ CONSISTENCY_CHECK(m_crawl.pFrame->IsTransitionToNativeFrame());
+ if (m_crawl.pFrame->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr())
+ {
+ // ControlPC may be different as the InlinedCallFrame stays active throughout
+ // the STOP_FOR_GC callout but we can use the stack/frame pointer for the assert.
+ PTR_InlinedCallFrame pICF = dac_cast<PTR_InlinedCallFrame>(m_crawl.pFrame);
+ CONSISTENCY_CHECK((GetRegdisplaySP(m_crawl.pRD) == (TADDR)pICF->GetCallSiteSP())
+ || (GetFP(m_crawl.pRD->pCurrentContext) == pICF->GetCalleeSavedFP()));
+ }
+ else
+ {
+ CONSISTENCY_CHECK(GetControlPC(m_crawl.pRD) == m_crawl.pFrame->GetReturnAddress());
+ }
+ }
+#endif // !defined(_TARGET_X86_) && defined(_DEBUG)
+
+#if defined(STACKWALKER_MAY_POP_FRAMES)
+ if (m_flags & POPFRAMES)
+ {
+ _ASSERTE(m_crawl.pFrame == m_crawl.pThread->GetFrame());
+
+ // If we got here, the current frame chose not to handle the
+ // exception. Give it a chance to do any termination work
+ // before we pop it off.
+
+ CLEAR_THREAD_TYPE_STACKWALKER();
+ END_FORBID_TYPELOAD();
+
+ m_crawl.pFrame->ExceptionUnwind();
+
+ BEGIN_FORBID_TYPELOAD();
+ SET_THREAD_TYPE_STACKWALKER(m_pThread);
+
+ // Pop off this frame and go on to the next one.
+ m_crawl.GotoNextFrame();
+
+ // When StackWalkFramesEx is originally called, we ensure
+ // that if POPFRAMES is set that the thread is in COOP mode
+ // and that running thread is walking itself. Thus, this
+ // COOP assertion is safe.
+ BEGIN_GCX_ASSERT_COOP;
+ m_crawl.pThread->SetFrame(m_crawl.pFrame);
+ END_GCX_ASSERT_COOP;
+ }
+ else
+#endif // STACKWALKER_MAY_POP_FRAMES
+ {
+ // go to the next frame
+ m_crawl.GotoNextFrame();
+ }
+
+ // check for skipped frames again
+ if (CheckForSkippedFrames())
+ {
+ // there are more skipped explicit frames
+ _ASSERTE(m_frameState == SFITER_SKIPPED_FRAME_FUNCTION);
+ goto Cleanup;
+ }
+ else
+ {
+#if defined(_TARGET_X86_)
+ // On x86, we process a managed stack frame before processing any explicit frames contained in it.
+ // So when we are done with the skipped explicit frame, we have already processed the managed
+ // stack frame, and it is time to move onto the next stack frame.
+ PostProcessingForManagedFrames();
+ if (m_frameState == SFITER_NATIVE_MARKER_FRAME)
+ {
+ goto Cleanup;
+ }
+#else // _TARGET_X86_
+ // We are done handling the skipped explicit frame at this point. So move on to the
+ // managed stack frame.
+ m_crawl.isFrameless = true;
+ m_crawl.codeInfo = m_cachedCodeInfo;
+ m_crawl.pFunc = m_crawl.codeInfo.GetMethodDesc();
+
+
+ PreProcessingForManagedFrames();
+ goto Cleanup;
+#endif // _TARGET_X86_
+ }
+ }
+ else if (m_frameState == SFITER_FRAMELESS_METHOD)
+ {
+ // Now find out if we need to leave monitors
+
+#ifdef _TARGET_X86_
+ //
+ // For non-x86 platforms, the JIT generates try/finally to leave monitors; for x86, the VM handles the monitor
+ //
+#if defined(STACKWALKER_MAY_POP_FRAMES)
+ if (m_flags & POPFRAMES)
+ {
+ BEGIN_GCX_ASSERT_COOP;
+
+ if (m_crawl.pFunc->IsSynchronized())
+ {
+ MethodDesc * pMD = m_crawl.pFunc;
+ OBJECTREF orUnwind = NULL;
+
+ if (m_crawl.GetCodeManager()->IsInSynchronizedRegion(m_crawl.GetRelOffset(),
+ m_crawl.GetGCInfo(),
+ m_crawl.GetCodeManagerFlags()))
+ {
+ if (pMD->IsStatic())
+ {
+ MethodTable * pMT = pMD->GetMethodTable();
+ orUnwind = pMT->GetManagedClassObjectIfExists();
+
+ _ASSERTE(orUnwind != NULL);
+ }
+ else
+ {
+ orUnwind = m_crawl.GetCodeManager()->GetInstance(
+ m_crawl.pRD,
+ m_crawl.GetCodeInfo());
+ }
+
+ _ASSERTE(orUnwind != NULL);
+ VALIDATEOBJECTREF(orUnwind);
+
+ _ASSERTE(!orUnwind->IsTransparentProxy());
+
+ if (orUnwind != NULL)
+ {
+ orUnwind->LeaveObjMonitorAtException();
+ }
+ }
+ }
+
+ END_GCX_ASSERT_COOP;
+ }
+#endif // STACKWALKER_MAY_POP_FRAMES
+#endif // _TARGET_X86_
+
+#if !defined(ELIMINATE_FEF)
+ // FaultingExceptionFrame is special case where it gets
+ // pushed on the stack after the frame is running
+ _ASSERTE((m_crawl.pFrame == FRAME_TOP) ||
+ ((TADDR)GetRegdisplaySP(m_crawl.pRD) < dac_cast<TADDR>(m_crawl.pFrame)) ||
+ (m_crawl.pFrame->GetVTablePtr() == FaultingExceptionFrame::GetMethodFrameVPtr()) ||
+ (m_crawl.pFrame->GetVTablePtr() == ContextTransitionFrame::GetMethodFrameVPtr()));
+#endif // !defined(ELIMINATE_FEF)
+
+ // Get rid of the frame (actually, it isn't really popped)
+
+ LOG((LF_GCROOTS, LL_EVERYTHING, "STACKWALK: [%03x] about to unwind for '%s', SP:" FMT_ADDR ", IP:" FMT_ADDR "\n",
+ m_uFramesProcessed,
+ m_crawl.pFunc->m_pszDebugMethodName,
+ DBG_ADDR(GetRegdisplaySP(m_crawl.pRD)),
+ DBG_ADDR(GetControlPC(m_crawl.pRD))));
+
+#if !defined(DACCESS_COMPILE)
+ StackwalkCacheEntry *pCacheEntry = m_crawl.GetStackwalkCacheEntry();
+ if (pCacheEntry != NULL)
+ {
+ _ASSERTE(m_crawl.stackWalkCache.Enabled() && (m_flags & LIGHTUNWIND));
+
+ // lightened schema: take stack unwind info from stackwalk cache
+ EECodeManager::QuickUnwindStackFrame(m_crawl.pRD, pCacheEntry, EECodeManager::UnwindCurrentStackFrame);
+ }
+ else
+#endif // !DACCESS_COMPILE
+ {
+#if !defined(DACCESS_COMPILE)
+ // non-optimized stack unwind schema, doesn't use StackwalkCache
+ UINT_PTR curSP = (UINT_PTR)GetRegdisplaySP(m_crawl.pRD);
+ UINT_PTR curIP = (UINT_PTR)GetControlPC(m_crawl.pRD);
+#endif // !DACCESS_COMPILE
+
+ bool fInsertCacheEntry = m_crawl.stackWalkCache.Enabled() &&
+ (m_flags & LIGHTUNWIND) &&
+ (m_pCachedGSCookie == NULL);
+
+ // Is this a dynamic method. Dynamic methods can be GC collected and so IP to method mapping
+ // is not persistent. Therefore do not cache information for this frame.
+ BOOL isCollectableMethod = ExecutionManager::IsCollectibleMethod(m_crawl.GetMethodToken());
+ if(isCollectableMethod)
+ fInsertCacheEntry = FALSE;
+
+ StackwalkCacheUnwindInfo unwindInfo;
+
+ if (!m_crawl.GetCodeManager()->UnwindStackFrame(
+ m_crawl.pRD,
+ &m_cachedCodeInfo,
+ m_codeManFlags
+ | m_crawl.GetCodeManagerFlags()
+ | ((m_flags & PROFILER_DO_STACK_SNAPSHOT) ? SpeculativeStackwalk : 0),
+ &m_crawl.codeManState,
+ (fInsertCacheEntry ? &unwindInfo : NULL)))
+ {
+ LOG((LF_CORPROF, LL_INFO100, "**PROF: m_crawl.GetCodeManager()->UnwindStackFrame failure leads to SWA_FAILED.\n"));
+ retVal = SWA_FAILED;
+ goto Cleanup;
+ }
+
+#if !defined(DACCESS_COMPILE)
+ // store into hashtable if fits, otherwise just use old schema
+ if (fInsertCacheEntry)
+ {
+ //
+ // information we add to cache, consists of two parts:
+ // 1. SPOffset - locals, etc. of current method, adding which to current ESP we get to retAddr ptr
+ // 2. argSize - size of pushed function arguments, the rest we need to add to get new ESP
+ // we have to store two parts of ESP delta, since we need to update pPC also, and so require retAddr ptr
+ //
+ // newSP = oldSP + SPOffset + sizeof(PTR) + argSize
+ //
+ UINT_PTR SPOffset = (UINT_PTR)GetRegdisplayStackMark(m_crawl.pRD) - curSP;
+ UINT_PTR argSize = (UINT_PTR)GetRegdisplaySP(m_crawl.pRD) - curSP - SPOffset - sizeof(void*);
+
+ StackwalkCacheEntry cacheEntry = {0};
+ if (cacheEntry.Init(
+ curIP,
+ SPOffset,
+ &unwindInfo,
+ argSize))
+ {
+ m_crawl.stackWalkCache.Insert(&cacheEntry);
+ }
+ }
+#endif // !DACCESS_COMPILE
+ }
+
+#define FAIL_IF_SPECULATIVE_WALK(condition) \
+ if (m_flags & PROFILER_DO_STACK_SNAPSHOT) \
+ { \
+ if (!(condition)) \
+ { \
+ LOG((LF_CORPROF, LL_INFO100, "**PROF: " #condition " failure leads to SWA_FAILED.\n")); \
+ retVal = SWA_FAILED; \
+ goto Cleanup; \
+ } \
+ } \
+ else \
+ { \
+ _ASSERTE(condition); \
+ }
+
+ // When the stackwalk is seeded with a profiler context, the context
+ // might be bogus. Check the stack pointer and the program counter for validity here.
+ // (Note that these checks are not strictly necessary since we are able
+ // to recover from AVs during profiler stackwalk.)
+
+ PTR_VOID newSP = PTR_VOID((TADDR)GetRegdisplaySP(m_crawl.pRD));
+ FAIL_IF_SPECULATIVE_WALK(newSP >= m_crawl.pThread->GetCachedStackLimit());
+ FAIL_IF_SPECULATIVE_WALK(newSP < m_crawl.pThread->GetCachedStackBase());
+
+#undef FAIL_IF_SPECULATIVE_WALK
+
+ LOG((LF_GCROOTS, LL_EVERYTHING, "STACKWALK: [%03x] finished unwind for '%s', SP:" FMT_ADDR \
+ ", IP:" FMT_ADDR "\n",
+ m_uFramesProcessed,
+ m_crawl.pFunc->m_pszDebugMethodName,
+ DBG_ADDR(GetRegdisplaySP(m_crawl.pRD)),
+ DBG_ADDR(GetControlPC(m_crawl.pRD))));
+
+ m_crawl.isFirst = FALSE;
+ m_crawl.isInterrupted = FALSE;
+ m_crawl.hasFaulted = FALSE;
+ m_crawl.isIPadjusted = FALSE;
+
+#if defined(_TARGET_X86_)
+ // remember, x86 handles the managed stack frame before the explicit frames contained in it
+ if (CheckForSkippedFrames())
+ {
+ _ASSERTE(m_frameState == SFITER_SKIPPED_FRAME_FUNCTION);
+ goto Cleanup;
+ }
+#endif // _TARGET_X86_
+
+ PostProcessingForManagedFrames();
+ if (m_frameState == SFITER_NATIVE_MARKER_FRAME)
+ {
+ goto Cleanup;
+ }
+ }
+ else if (m_frameState == SFITER_FRAME_FUNCTION)
+ {
+ Frame* pInlinedFrame = NULL;
+
+ if (InlinedCallFrame::FrameHasActiveCall(m_crawl.pFrame))
+ {
+ pInlinedFrame = m_crawl.pFrame;
+ }
+
+ unsigned uFrameAttribs = m_crawl.pFrame->GetFrameAttribs();
+
+ // Special resumable frames make believe they are on top of the stack.
+ m_crawl.isFirst = (uFrameAttribs & Frame::FRAME_ATTR_RESUMABLE) != 0;
+
+ // If the frame is a subclass of ExceptionFrame,
+ // then we know this is interrupted.
+ m_crawl.isInterrupted = (uFrameAttribs & Frame::FRAME_ATTR_EXCEPTION) != 0;
+
+ if (m_crawl.isInterrupted)
+ {
+ m_crawl.hasFaulted = (uFrameAttribs & Frame::FRAME_ATTR_FAULTED) != 0;
+ m_crawl.isIPadjusted = (uFrameAttribs & Frame::FRAME_ATTR_OUT_OF_LINE) != 0;
+ _ASSERTE(!m_crawl.hasFaulted || !m_crawl.isIPadjusted); // both cant be set together
+ }
+
+ //
+ // Update app domain if this frame caused a transition.
+ //
+
+ AppDomain *retDomain = m_crawl.pFrame->GetReturnDomain();
+ if (retDomain != NULL)
+ {
+ m_crawl.pAppDomain = retDomain;
+ }
+
+ PCODE adr = m_crawl.pFrame->GetReturnAddress();
+ _ASSERTE(adr != (PCODE)POISONC);
+
+ _ASSERTE(!pInlinedFrame || adr);
+
+ if (adr)
+ {
+ ProcessIp(adr);
+
+ _ASSERTE(m_crawl.GetCodeInfo()->IsValid() || !pInlinedFrame);
+
+ if (m_crawl.isFrameless)
+ {
+ m_crawl.pFrame->UpdateRegDisplay(m_crawl.pRD);
+
+#if !defined(_TARGET_X86_)
+ CONSISTENCY_CHECK(NULL == m_pvResumableFrameTargetSP);
+
+ if (m_crawl.isFirst)
+ {
+ if (m_flags & THREAD_IS_SUSPENDED)
+ {
+ _ASSERTE(m_crawl.isProfilerDoStackSnapshot);
+
+ // abort the stackwalk, we can't proceed without risking deadlock
+ retVal = SWA_FAILED;
+ goto Cleanup;
+ }
+
+ // we are about to unwind, which may take a lock, so the thread
+ // better not be suspended.
+ CONSISTENCY_CHECK(!(m_flags & THREAD_IS_SUSPENDED));
+
+#if !defined(DACCESS_COMPILE)
+ if (m_crawl.stackWalkCache.Enabled() && (m_flags & LIGHTUNWIND))
+ {
+ m_crawl.isCachedMethod = m_crawl.stackWalkCache.Lookup((UINT_PTR)adr);
+ }
+#endif // DACCESS_COMPILE
+
+ EECodeManager::EnsureCallerContextIsValid(m_crawl.pRD, m_crawl.GetStackwalkCacheEntry());
+ m_pvResumableFrameTargetSP = (LPVOID)GetSP(m_crawl.pRD->pCallerContext);
+ }
+#endif // !_TARGET_X86_
+
+
+ // We are transitioning from unmanaged code to managed code... lets do some validation of our
+ // EH mechanism on platforms that we can.
+#if defined(_DEBUG) && !defined(DACCESS_COMPILE) && defined(_TARGET_X86_)
+ VerifyValidTransitionFromManagedCode(m_crawl.pThread, &m_crawl);
+#endif // _DEBUG && !DACCESS_COMPILE && _TARGET_X86_
+ }
+ }
+
+ if (!pInlinedFrame)
+ {
+#if defined(STACKWALKER_MAY_POP_FRAMES)
+ if (m_flags & POPFRAMES)
+ {
+ // If we got here, the current frame chose not to handle the
+ // exception. Give it a chance to do any termination work
+ // before we pop it off.
+
+ CLEAR_THREAD_TYPE_STACKWALKER();
+ END_FORBID_TYPELOAD();
+
+ m_crawl.pFrame->ExceptionUnwind();
+
+ BEGIN_FORBID_TYPELOAD();
+ SET_THREAD_TYPE_STACKWALKER(m_pThread);
+
+ // Pop off this frame and go on to the next one.
+ m_crawl.GotoNextFrame();
+
+ // When StackWalkFramesEx is originally called, we ensure
+ // that if POPFRAMES is set that the thread is in COOP mode
+ // and that running thread is walking itself. Thus, this
+ // COOP assertion is safe.
+ BEGIN_GCX_ASSERT_COOP;
+ m_crawl.pThread->SetFrame(m_crawl.pFrame);
+ END_GCX_ASSERT_COOP;
+ }
+ else
+#endif // STACKWALKER_MAY_POP_FRAMES
+ {
+ // Go to the next frame.
+ m_crawl.GotoNextFrame();
+ }
+ }
+ }
+#if defined(ELIMINATE_FEF)
+ else if (m_frameState == SFITER_NO_FRAME_TRANSITION)
+ {
+ PostProcessingForNoFrameTransition();
+ }
+#endif // ELIMINATE_FEF
+ else if (m_frameState == SFITER_NATIVE_MARKER_FRAME)
+ {
+ m_crawl.isNativeMarker = false;
+ }
+ else if (m_frameState == SFITER_INITIAL_NATIVE_CONTEXT)
+ {
+ // nothing to do here
+ }
+ else
+ {
+ _ASSERTE(m_frameState == SFITER_UNINITIALIZED);
+ _ASSERTE(!"StackFrameIterator::NextRaw() called when the iterator is uninitialized. \
+ Should never get here.");
+ retVal = SWA_FAILED;
+ goto Cleanup;
+ }
+
+ ProcessCurrentFrame();
+
+Cleanup:
+#if defined(_DEBUG)
+ if (retVal == SWA_FAILED)
+ {
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: SWA_FAILED: couldn't start stackwalk\n"));
+ }
+#endif // _DEBUG
+
+ return retVal;
+} // StackFrameIterator::NextRaw()
+
+//---------------------------------------------------------------------------------------
+//
+// Synchronizing the REGDISPLAY to the current CONTEXT stored in the REGDISPLAY.
+// This is an nop on non-WIN64 platforms.
+//
+
+void StackFrameIterator::UpdateRegDisp(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ WIN64_ONLY(SyncRegDisplayToCurrentContext(m_crawl.pRD));
+} // StackFrameIterator::UpdateRegDisp()
+
+//---------------------------------------------------------------------------------------
+//
+// Check whether the specified Ip is in managed code and update the CrawlFrame accordingly.
+// This function updates isFrameless, JitManagerInstance.
+//
+// Arguments:
+// Ip - IP to be processed
+//
+
+void StackFrameIterator::ProcessIp(PCODE Ip)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ // Re-initialize codeInfo with new IP
+ m_crawl.codeInfo.Init(Ip, m_scanFlag);
+
+ m_crawl.isFrameless = !!m_crawl.codeInfo.IsValid();
+} // StackFrameIterator::ProcessIp()
+
+//---------------------------------------------------------------------------------------
+//
+// Update the CrawlFrame to represent where we have stopped. This is called after advancing
+// to a new frame.
+//
+// Notes:
+// This function and everything it calls must not rely on m_frameState, which could have become invalid
+// when we advance the iterator before calling this function.
+//
+
+void StackFrameIterator::ProcessCurrentFrame(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ bool fDone = false;
+
+ m_crawl.CheckGSCookies();
+
+ // Since we have advanced the iterator, the frame state represents the previous frame state,
+ // not the current one. This is important to keep in mind. Ideally we should just assert that
+ // the frame state has been set to invalid upon entry to this function, but we need the previous frame
+ // state to decide if we should stop at an native stack frame.
+
+ // If we just do a simple check for native code here, we will loop forever.
+ if (m_frameState == SFITER_UNINITIALIZED)
+ {
+ // "!IsFrameless()" normally implies that the CrawlFrame is at an explicit frame. Here we are using it
+ // to detect whether the CONTEXT is in managed code or not. Ideally we should have a enum on the
+ // CrawlFrame to indicate the various types of "frames" the CrawlFrame can stop at.
+ //
+ // If the CONTEXT is in native code and the StackFrameIterator is uninitialized, then it must be
+ // an initial native CONTEXT passed to the StackFrameIterator when it is created or
+ // when ResetRegDisp() is called.
+ if (!m_crawl.IsFrameless())
+ {
+ m_frameState = SFITER_INITIAL_NATIVE_CONTEXT;
+ fDone = true;
+ }
+ }
+ else
+ {
+ // Clear the frame state. It will be set before we return from this function.
+ m_frameState = SFITER_UNINITIALIZED;
+ }
+
+ // Check for the case of an exception in managed code, and resync the stack walk
+ // from the exception context.
+#if defined(ELIMINATE_FEF)
+ if (!fDone && !m_crawl.IsFrameless() && m_exInfoWalk.GetExInfo())
+ {
+ // We are currently walking ("lost") in unmanaged code. We can recover
+ // from a) the next Frame record, or b) an exception context.
+ // Recover from the exception context if all of these are true:
+ // - it "returns" to managed code
+ // - if is lower (newer) than the next Frame record
+ // - the stack walk has not already passed by it
+ //
+ // The ExInfo walker is initialized to be higher than the pStartFrame, and
+ // as we unwind managed (frameless) functions, we keep eliminating any
+ // ExInfos that are passed in the stackwalk.
+ //
+ // So, here we need to find the next ExInfo that "returns" to managed code,
+ // and then choose the lower of that ExInfo and the next Frame.
+ m_exInfoWalk.WalkToManaged();
+ TADDR pContextSP = m_exInfoWalk.GetSPFromContext();
+
+ //@todo: check the exception code for a fault?
+
+ // If there was a pContext that is higher than the SP and starting frame...
+ if (pContextSP)
+ {
+ PTR_CONTEXT pContext = m_exInfoWalk.GetContext();
+
+ LOG((LF_EH, LL_INFO10000, "STACKWALK: considering resync from pContext(%p), fault(%08X), sp(%p); \
+ pStartFrame(%p); cf.pFrame(%p), cf.SP(%p)\n",
+ pContext, m_exInfoWalk.GetFault(), pContextSP,
+ m_pStartFrame, dac_cast<TADDR>(m_crawl.pFrame), GetRegdisplaySP(m_crawl.pRD)));
+
+ // If the pContext is lower (newer) than the CrawlFrame's Frame*, try to use
+ // the pContext.
+ // There are still a few cases in which a FaultingExceptionFrame is linked in. If
+ // the next frame is one of them, we don't want to override it. THIS IS PROBABLY BAD!!!
+ if ( (pContextSP < dac_cast<TADDR>(m_crawl.pFrame)) &&
+ ((m_crawl.GetFrame() == FRAME_TOP) ||
+ (m_crawl.GetFrame()->GetVTablePtr() != FaultingExceptionFrame::GetMethodFrameVPtr() ) ) )
+ {
+ //
+ // If the REGDISPLAY represents an unmanaged stack frame above (closer to the leaf than) an
+ // ExInfo without any intervening managed stack frame, then we will stop at the no-frame
+ // transition protected by the ExInfo. However, if the unmanaged stack frame is the one
+ // immediately above the faulting managed stack frame, we want to continue the stackwalk
+ // with the faulting managed stack frame. So we do not stop in this case.
+ //
+ // However, just comparing EBP is not enough. The OS exception handler
+ // (KiUserExceptionDispatcher()) does not use an EBP frame. So if we just compare the EBP
+ // we will think that the OS excpetion handler is the one we want to claim. Instead,
+ // we should also check the current IP, which because of the way unwinding work and
+ // how the OS exception handler behaves is actually going to be the stack limit of the
+ // current thread. This is of course a workaround and is dependent on the OS behaviour.
+ //
+
+ PCODE curPC = GetControlPC(m_crawl.pRD);
+ if ((m_crawl.pRD->pEbp != NULL ) &&
+ (m_exInfoWalk.GetEBPFromContext() == GetRegdisplayFP(m_crawl.pRD)) &&
+ ((m_crawl.pThread->GetCachedStackLimit() <= PTR_VOID(curPC)) &&
+ (PTR_VOID(curPC) < m_crawl.pThread->GetCachedStackBase())))
+ {
+ // restore the CONTEXT saved by the ExInfo and continue on to the faulting
+ // managed stack frame
+ PostProcessingForNoFrameTransition();
+ }
+ else
+ {
+ // we stop stop at the no-frame transition
+ m_frameState = SFITER_NO_FRAME_TRANSITION;
+ m_crawl.isNoFrameTransition = true;
+ m_crawl.taNoFrameTransitionMarker = pContextSP;
+ fDone = true;
+ }
+ }
+ }
+ }
+#endif // defined(ELIMINATE_FEF)
+
+ if (!fDone)
+ {
+ // returns SWA_DONE if there is no more frames to walk
+ if (!IsValid())
+ {
+ LOG((LF_GCROOTS, LL_INFO10000, "STACKWALK: SWA_DONE: reached the end of the stack\n"));
+ m_frameState = SFITER_DONE;
+ return;
+ }
+
+ m_crawl.codeManState.dwIsSet = 0;
+#if defined(_DEBUG)
+ memset((void *)m_crawl.codeManState.stateBuf, 0xCD,
+ sizeof(m_crawl.codeManState.stateBuf));
+#endif // _DEBUG
+
+ if (m_crawl.isFrameless)
+ {
+ //------------------------------------------------------------------------
+ // This must be a JITed/managed native method. There is no explicit frame.
+ //------------------------------------------------------------------------
+
+#if !defined(DACCESS_COMPILE)
+ m_crawl.isCachedMethod = FALSE;
+ if (m_crawl.stackWalkCache.Enabled() && (m_flags & LIGHTUNWIND))
+ {
+ m_crawl.isCachedMethod = m_crawl.stackWalkCache.Lookup((UINT_PTR)GetControlPC(m_crawl.pRD));
+ _ASSERTE (m_crawl.isCachedMethod != m_crawl.stackWalkCache.IsEmpty());
+
+ m_crawl.pSecurityObject = NULL;
+#if defined(_TARGET_X86_)
+ if (m_crawl.isCachedMethod && m_crawl.stackWalkCache.m_CacheEntry.HasSecurityObject())
+ {
+ // pCallback will use this to save time on GetAddrOfSecurityObject
+ StackwalkCacheUnwindInfo stackwalkCacheUnwindInfo(&m_crawl.stackWalkCache.m_CacheEntry);
+ m_crawl.pSecurityObject = EECodeManager::GetAddrOfSecurityObjectFromCachedInfo(
+ m_crawl.pRD,
+ &stackwalkCacheUnwindInfo);
+ }
+#endif // _TARGET_X86_
+ }
+#endif // DACCESS_COMPILE
+
+
+#if defined(WIN64EXCEPTIONS)
+ m_crawl.isFilterFuncletCached = false;
+#endif // WIN64EXCEPTIONS
+
+ m_crawl.pFunc = m_crawl.codeInfo.GetMethodDesc();
+
+ // Cache values which may be updated by CheckForSkippedFrames()
+ m_cachedCodeInfo = m_crawl.codeInfo;
+
+#if !defined(_TARGET_X86_)
+ // On non-X86, we want to process the skipped explicit frames before the managed stack frame
+ // containing them.
+ if (CheckForSkippedFrames())
+ {
+ _ASSERTE(m_frameState == SFITER_SKIPPED_FRAME_FUNCTION);
+ }
+ else
+#endif // !_TARGET_X86_
+ {
+ PreProcessingForManagedFrames();
+ _ASSERTE(m_frameState == SFITER_FRAMELESS_METHOD);
+ }
+ }
+ else
+ {
+ INDEBUG(m_crawl.pThread->DebugLogStackWalkInfo(&m_crawl, "CONSIDER", m_uFramesProcessed));
+
+ _ASSERTE(m_crawl.pFrame != FRAME_TOP);
+
+ m_crawl.pFunc = m_crawl.pFrame->GetFunction();
+
+ m_frameState = SFITER_FRAME_FUNCTION;
+ }
+ }
+
+ _ASSERTE(m_frameState != SFITER_UNINITIALIZED);
+} // StackFrameIterator::ProcessCurrentFrame()
+
+//---------------------------------------------------------------------------------------
+//
+// If an explicit frame is allocated in a managed stack frame (e.g. an inlined pinvoke call),
+// we may have skipped an explicit frame. This function checks for them.
+//
+// Return Value:
+// Returns true if there are skipped frames.
+//
+// Notes:
+// x86 wants to stop at the skipped stack frames after the containing managed stack frame, but
+// WIN64 wants to stop before. I don't think x86 actually has any good reason for this, except
+// because it doesn't unwind one frame ahead of time like WIN64 does. This means that we don't
+// have the caller SP on x86.
+//
+
+BOOL StackFrameIterator::CheckForSkippedFrames(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ BOOL fHandleSkippedFrames = FALSE;
+ TADDR pvReferenceSP;
+
+ // Can the caller handle skipped frames;
+ fHandleSkippedFrames = (m_flags & HANDLESKIPPEDFRAMES);
+
+#if defined(_TARGET_X86_)
+ pvReferenceSP = GetRegdisplaySP(m_crawl.pRD);
+#else // _TARGET_X86_
+ // Order the Frames relative to the caller SP of the methods
+ // this makes it so that any Frame that is in a managed call
+ // frame will be reported before its containing method.
+
+ // This should always succeed! If it doesn't, it's a bug somewhere else!
+ EECodeManager::EnsureCallerContextIsValid(m_crawl.pRD, m_crawl.GetStackwalkCacheEntry(), &m_cachedCodeInfo);
+ pvReferenceSP = GetSP(m_crawl.pRD->pCallerContext);
+#endif // _TARGET_X86_
+
+ if ( !( (m_crawl.pFrame != FRAME_TOP) &&
+ (dac_cast<TADDR>(m_crawl.pFrame) < pvReferenceSP) )
+ )
+ {
+ return FALSE;
+ }
+
+ LOG((LF_GCROOTS, LL_EVERYTHING, "STACKWALK: CheckForSkippedFrames\n"));
+
+ // We might have skipped past some Frames.
+ // This happens with InlinedCallFrames and if we unwound
+ // out of a finally in managed code or for ContextTransitionFrames
+ // that are inserted into the managed call stack.
+ while ( (m_crawl.pFrame != FRAME_TOP) &&
+ (dac_cast<TADDR>(m_crawl.pFrame) < pvReferenceSP)
+ )
+ {
+ BOOL fReportInteropMD =
+ // If we see InlinedCallFrame in certain IL stubs, we should report the MD that
+ // was passed to the stub as its secret argument. This is the true interop MD.
+ // Note that code:InlinedCallFrame.GetFunction may return NULL in this case because
+ // the call is made using the CALLI instruction.
+ m_crawl.pFrame != FRAME_TOP &&
+ m_crawl.pFrame->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr() &&
+ m_crawl.pFunc != NULL &&
+ m_crawl.pFunc->IsILStub() &&
+ m_crawl.pFunc->AsDynamicMethodDesc()->HasMDContextArg();
+
+ if (fHandleSkippedFrames
+#ifdef _TARGET_X86_
+ || // On x86 we have already reported the InlinedCallFrame, don't report it again.
+ (InlinedCallFrame::FrameHasActiveCall(m_crawl.pFrame) && !fReportInteropMD)
+#endif // _TARGET_X86_
+ )
+ {
+ m_crawl.GotoNextFrame();
+#ifdef STACKWALKER_MAY_POP_FRAMES
+ if (m_flags & POPFRAMES)
+ {
+ // When StackWalkFramesEx is originally called, we ensure
+ // that if POPFRAMES is set that the thread is in COOP mode
+ // and that running thread is walking itself. Thus, this
+ // COOP assertion is safe.
+ BEGIN_GCX_ASSERT_COOP;
+ m_crawl.pThread->SetFrame(m_crawl.pFrame);
+ END_GCX_ASSERT_COOP;
+ }
+#endif // STACKWALKER_MAY_POP_FRAMES
+ }
+ else
+ {
+ m_crawl.isFrameless = false;
+
+ if (fReportInteropMD)
+ {
+ m_crawl.pFunc = ((PTR_InlinedCallFrame)m_crawl.pFrame)->GetActualInteropMethodDesc();
+ _ASSERTE(m_crawl.pFunc != NULL);
+ _ASSERTE(m_crawl.pFunc->SanityCheck());
+ }
+ else
+ {
+ m_crawl.pFunc = m_crawl.pFrame->GetFunction();
+ }
+
+ INDEBUG(m_crawl.pThread->DebugLogStackWalkInfo(&m_crawl, "CONSIDER", m_uFramesProcessed));
+
+ m_frameState = SFITER_SKIPPED_FRAME_FUNCTION;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+} // StackFrameIterator::CheckForSkippedFrames()
+
+//---------------------------------------------------------------------------------------
+//
+// Perform the necessary tasks before stopping at a managed stack frame. This is mostly validation work.
+//
+
+void StackFrameIterator::PreProcessingForManagedFrames(void)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+#if !defined(_TARGET_X86_)
+ if (m_pvResumableFrameTargetSP)
+ {
+ // We expect that if we saw a resumable frame, the next managed
+ // IP that we see will be the one the resumable frame took us to.
+
+ // However, because we might visit intervening explicit Frames
+ // that will clear the .isFirst flag, we need to set it back here.
+
+ CONSISTENCY_CHECK(m_crawl.pRD->IsCallerContextValid);
+ CONSISTENCY_CHECK((LPVOID)GetSP(m_crawl.pRD->pCallerContext) == m_pvResumableFrameTargetSP);
+ m_pvResumableFrameTargetSP = NULL;
+ m_crawl.isFirst = true;
+ }
+#endif // !_TARGET_X86_
+
+#if !defined(DACCESS_COMPILE)
+ m_pCachedGSCookie = (GSCookie*)m_crawl.GetCodeManager()->GetGSCookieAddr(
+ m_crawl.pRD,
+ &m_crawl.codeInfo,
+ &m_crawl.codeManState);
+#endif // !DACCESS_COMPILE
+
+ if (m_pCachedGSCookie)
+ {
+ m_crawl.SetCurGSCookie(m_pCachedGSCookie);
+ }
+
+ INDEBUG(m_crawl.pThread->DebugLogStackWalkInfo(&m_crawl, "CONSIDER", m_uFramesProcessed));
+
+#if defined(_DEBUG) && defined(_TARGET_X86_) && !defined(DACCESS_COMPILE)
+ // m_crawl.GetThisPointer() requires full unwind
+ // In GC's relocate phase, objects is not verifiable
+ if ( !(m_flags & (LIGHTUNWIND | QUICKUNWIND | ALLOW_INVALID_OBJECTS)) &&
+ m_crawl.pFunc->IsSynchronized() &&
+ !m_crawl.pFunc->IsStatic() &&
+ m_crawl.GetCodeManager()->IsInSynchronizedRegion(m_crawl.GetRelOffset(),
+ m_crawl.GetGCInfo(),
+ m_crawl.GetCodeManagerFlags()))
+ {
+ BEGIN_GCX_ASSERT_COOP;
+
+ OBJECTREF obj = m_crawl.GetThisPointer();
+
+ _ASSERTE(obj != NULL);
+ VALIDATEOBJECTREF(obj);
+
+ DWORD threadId = 0;
+ DWORD acquisitionCount = 0;
+ _ASSERTE(obj->GetThreadOwningMonitorLock(&threadId, &acquisitionCount) &&
+ (threadId == m_crawl.pThread->GetThreadId()));
+
+ END_GCX_ASSERT_COOP;
+ }
+#endif // _DEBUG && _TARGET_X86_ && !DACCESS_COMPILE
+
+ m_frameState = SFITER_FRAMELESS_METHOD;
+} // StackFrameIterator::PreProcessingForManagedFrames()
+
+//---------------------------------------------------------------------------------------
+//
+// Perform the necessary tasks after stopping at a managed stack frame and unwinding to its caller.
+// This includes advancing the ExInfo and checking whether the new IP is managed.
+//
+
+void StackFrameIterator::PostProcessingForManagedFrames(void)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ MODE_ANY;
+ SUPPORTS_DAC;
+ }
+ CONTRACTL_END;
+
+
+#if defined(ELIMINATE_FEF)
+ // As with frames, we may have unwound past a ExInfo.pContext. This
+ // can happen when unwinding from a handler that rethrew the exception.
+ // Skip any ExInfo.pContext records that may no longer be valid.
+ // If Frames would be unlinked from the Frame chain, also reset the UseExInfoForStackwalk bit
+ // on the ExInfo.
+ m_exInfoWalk.WalkToPosition(GetRegdisplaySP(m_crawl.pRD), (m_flags & POPFRAMES));
+#endif // ELIMINATE_FEF
+
+ ProcessIp(GetControlPC(m_crawl.pRD));
+
+ // if we have unwound to a native stack frame, stop and set the frame state accordingly
+ if (!m_crawl.isFrameless)
+ {
+ m_frameState = SFITER_NATIVE_MARKER_FRAME;
+ m_crawl.isNativeMarker = true;
+ }
+} // StackFrameIterator::PostProcessingForManagedFrames()
+
+//---------------------------------------------------------------------------------------
+//
+// Perform the necessary tasks after stopping at a no-frame transition. This includes loading
+// the CONTEXT stored in the ExInfo and updating the REGDISPLAY to the faulting managed stack frame.
+//
+
+void StackFrameIterator::PostProcessingForNoFrameTransition()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ MODE_ANY;
+ SUPPORTS_DAC;
+ }
+ CONTRACTL_END;
+
+#if defined(ELIMINATE_FEF)
+ PTR_CONTEXT pContext = m_exInfoWalk.GetContext();
+
+ // Get the JitManager for the managed address.
+ m_crawl.codeInfo.Init(GetIP(pContext), m_scanFlag);
+ _ASSERTE(m_crawl.codeInfo.IsValid());
+
+ STRESS_LOG4(LF_EH, LL_INFO100, "STACKWALK: resync from pContext(%p); pStartFrame(%p), \
+ cf.pFrame(%p), cf.SP(%p)\n",
+ dac_cast<TADDR>(pContext), dac_cast<TADDR>(m_pStartFrame), dac_cast<TADDR>(m_crawl.pFrame),
+ GetRegdisplaySP(m_crawl.pRD));
+
+ // Update the RegDisplay from the context info.
+ FillRegDisplay(m_crawl.pRD, pContext);
+
+ // Now we know where we are, and it's "frameless", aka managed.
+ m_crawl.isFrameless = true;
+
+ // Flags the same as from a FaultingExceptionFrame.
+ m_crawl.isInterrupted = 1;
+ m_crawl.hasFaulted = 1;
+ m_crawl.isIPadjusted = 0;
+
+#if defined(STACKWALKER_MAY_POP_FRAMES)
+ // If Frames would be unlinked from the Frame chain, also reset the UseExInfoForStackwalk bit
+ // on the ExInfo.
+ if (m_flags & POPFRAMES)
+ {
+ m_exInfoWalk.GetExInfo()->m_ExceptionFlags.ResetUseExInfoForStackwalk();
+ }
+#endif // STACKWALKER_MAY_POP_FRAMES
+
+ // Done with this ExInfo.
+ m_exInfoWalk.WalkOne();
+
+ m_crawl.isNoFrameTransition = false;
+ m_crawl.taNoFrameTransitionMarker = NULL;
+#endif // ELIMINATE_FEF
+} // StackFrameIterator::PostProcessingForNoFrameTransition()
+
+
+#if defined(_TARGET_AMD64_) && !defined(DACCESS_COMPILE)
+static CrstStatic g_StackwalkCacheLock; // Global StackwalkCache lock; only used on AMD64
+EXTERN_C void moveOWord(LPVOID src, LPVOID target);
+#endif // _TARGET_AMD64_
+
+/*
+ copies 64-bit *src to *target, atomically accessing the data
+ requires 64-bit alignment for atomic load/store
+*/
+inline static void atomicMoveCacheEntry(UINT64* src, UINT64* target)
+{
+ LIMITED_METHOD_CONTRACT;
+
+#ifdef _TARGET_X86_
+ // the most negative value is used a sort of integer infinity
+ // value, so it have to be avoided
+ _ASSERTE(*src != 0x8000000000000000);
+ __asm
+ {
+ mov eax, src
+ fild qword ptr [eax]
+ mov eax, target
+ fistp qword ptr [eax]
+ }
+#elif defined(_TARGET_AMD64_) && !defined(DACCESS_COMPILE)
+ // On AMD64 there's no way to move 16 bytes atomically, so we need to take a lock before calling moveOWord().
+ CrstHolder ch(&g_StackwalkCacheLock);
+ moveOWord(src, target);
+#endif
+}
+
+/*
+============================================================
+Here is an implementation of StackwalkCache class, used to optimize performance
+of stack walking. Currently each CrawlFrame has a StackwalkCache member, which implements
+functionality for caching already walked methods (see Thread::StackWalkFramesEx).
+See class and corresponding types declaration at stackwalktypes.h
+We do use global cache g_StackwalkCache[] with InterlockCompareExchange, fitting
+each cache entry into 8 bytes.
+============================================================
+*/
+
+#ifndef DACCESS_COMPILE
+#define LOG_NUM_OF_CACHE_ENTRIES 10
+#else
+// Stack walk cache is disabled in DAC - save space
+#define LOG_NUM_OF_CACHE_ENTRIES 0
+#endif
+#define NUM_OF_CACHE_ENTRIES (1 << LOG_NUM_OF_CACHE_ENTRIES)
+
+static StackwalkCacheEntry g_StackwalkCache[NUM_OF_CACHE_ENTRIES] = {}; // Global StackwalkCache
+
+#ifdef DACCESS_COMPILE
+const BOOL StackwalkCache::s_Enabled = FALSE;
+#else
+BOOL StackwalkCache::s_Enabled = FALSE;
+
+/*
+ StackwalkCache class constructor.
+ Set "enable/disable optimization" flag according to registry key.
+*/
+StackwalkCache::StackwalkCache()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+ ClearEntry();
+
+ static BOOL stackwalkCacheEnableChecked = FALSE;
+ if (!stackwalkCacheEnableChecked)
+ {
+ // We can enter this block on multiple threads because of racing.
+ // However, that is OK since this operation is idempotent
+
+ s_Enabled = ((g_pConfig->DisableStackwalkCache() == 0) &&
+ // disable cache if for some reason it is not aligned
+ IS_ALIGNED((void*)&g_StackwalkCache[0], STACKWALK_CACHE_ENTRY_ALIGN_BOUNDARY));
+ stackwalkCacheEnableChecked = TRUE;
+ }
+}
+
+#endif // #ifndef DACCESS_COMPILE
+
+// static
+void StackwalkCache::Init()
+{
+#if defined(_TARGET_AMD64_) && !defined(DACCESS_COMPILE)
+ g_StackwalkCacheLock.Init(CrstSecurityStackwalkCache, CRST_UNSAFE_ANYMODE);
+#endif // _TARGET_AMD64_
+}
+
+/*
+ Returns efficient hash table key based on provided IP.
+ CPU architecture dependent.
+*/
+inline unsigned StackwalkCache::GetKey(UINT_PTR IP)
+{
+ LIMITED_METHOD_CONTRACT;
+ return (unsigned)(((IP >> LOG_NUM_OF_CACHE_ENTRIES) ^ IP) & (NUM_OF_CACHE_ENTRIES-1));
+}
+
+/*
+ Looks into cache and returns StackwalkCache entry, if current IP is cached.
+ JIT team guarantees the same ESP offset for the same IPs for different call chains.
+*/
+BOOL StackwalkCache::Lookup(UINT_PTR IP)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ _ASSERTE(Enabled());
+ _ASSERTE(IP);
+
+ unsigned hkey = GetKey(IP);
+ _ASSERTE(IS_ALIGNED((void*)&g_StackwalkCache[hkey], STACKWALK_CACHE_ENTRY_ALIGN_BOUNDARY));
+ // Don't care about m_CacheEntry access atomicity, since it's private to this
+ // stackwalk/thread
+ atomicMoveCacheEntry((UINT64*)&g_StackwalkCache[hkey], (UINT64*)&m_CacheEntry);
+
+#ifdef _DEBUG
+ if (IP != m_CacheEntry.IP)
+ {
+ ClearEntry();
+ }
+#endif
+
+ return (IP == m_CacheEntry.IP);
+#else // _TARGET_X86_
+ return FALSE;
+#endif // _TARGET_X86_
+}
+
+/*
+ Caches data provided for current IP.
+*/
+void StackwalkCache::Insert(StackwalkCacheEntry *pCacheEntry)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+ _ASSERTE(Enabled());
+ _ASSERTE(pCacheEntry);
+
+ unsigned hkey = GetKey(pCacheEntry->IP);
+ _ASSERTE(IS_ALIGNED((void*)&g_StackwalkCache[hkey], STACKWALK_CACHE_ENTRY_ALIGN_BOUNDARY));
+ atomicMoveCacheEntry((UINT64*)pCacheEntry, (UINT64*)&g_StackwalkCache[hkey]);
+}
+
+// static
+void StackwalkCache::Invalidate(LoaderAllocator * pLoaderAllocator)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+ if (!s_Enabled)
+ return;
+
+ /* Note that we could just flush the entries corresponding to
+ pDomain if we wanted to get fancy. To keep things simple for now,
+ we just invalidate everything
+ */
+
+ ZeroMemory(PVOID(&g_StackwalkCache), sizeof(g_StackwalkCache));
+}
+
+//----------------------------------------------------------------------------
+//
+// SetUpRegdisplayForStackWalk - set up Regdisplay for a stack walk
+//
+// Arguments:
+// pThread - pointer to the managed thread to be crawled
+// pContext - pointer to the context
+// pRegdisplay - pointer to the REGDISPLAY to be filled
+//
+// Return Value:
+// None
+//
+//----------------------------------------------------------------------------
+void SetUpRegdisplayForStackWalk(Thread * pThread, T_CONTEXT * pContext, REGDISPLAY * pRegdisplay)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ } CONTRACTL_END;
+
+ // @dbgtodo filter CONTEXT- The filter CONTEXT will be removed in V3.0.
+ T_CONTEXT * pFilterContext = pThread->GetFilterContext();
+ _ASSERTE(!(pFilterContext && ISREDIRECTEDTHREAD(pThread)));
+
+ if (pFilterContext != NULL)
+ {
+ FillRegDisplay(pRegdisplay, pFilterContext);
+ }
+ else
+ {
+ ZeroMemory(pContext, sizeof(*pContext));
+ FillRegDisplay(pRegdisplay, pContext);
+
+ if (ISREDIRECTEDTHREAD(pThread))
+ {
+ pThread->GetFrame()->UpdateRegDisplay(pRegdisplay);
+ }
+ }
+}