diff options
Diffstat (limited to 'src/debug/ee/frameinfo.cpp')
-rw-r--r-- | src/debug/ee/frameinfo.cpp | 2211 |
1 files changed, 2211 insertions, 0 deletions
diff --git a/src/debug/ee/frameinfo.cpp b/src/debug/ee/frameinfo.cpp new file mode 100644 index 0000000000..35e5bb9a09 --- /dev/null +++ b/src/debug/ee/frameinfo.cpp @@ -0,0 +1,2211 @@ +// 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. +//***************************************************************************** +// File: frameinfo.cpp +// + +// +// Code to find control info about a stack frame. +// +//***************************************************************************** + +#include "stdafx.h" + +// Include so we can get information out of ComMethodFrame +#ifdef FEATURE_COMINTEROP +#include "COMToClrCall.h" +#endif + +// Get a frame pointer from a RegDisplay. +// This is mostly used for chains and stub frames (i.e. internal frames), where we don't need an exact +// frame pointer. This is why it is okay to use the current SP instead of the caller SP on IA64. +// We should really rename this and possibly roll it into GetFramePointer() when we move the stackwalker +// to OOP. +FramePointer GetSP(REGDISPLAY * pRDSrc) +{ + FramePointer fp = FramePointer::MakeFramePointer( + (LPVOID)GetRegdisplaySP(pRDSrc)); + + return fp; +} + +// Get a frame pointer from a RegDisplay. +FramePointer GetFramePointer(REGDISPLAY * pRDSrc) +{ + return FramePointer::MakeFramePointer(GetRegdisplaySP(pRDSrc)); +} + +//--------------------------------------------------------------------------------------- +// +// Convert a FramePointer to a StackFrame and return it. +// +// Arguments: +// fp - the FramePointer to be converted +// +// Return Value: +// a StackFrame equivalent to the given FramePointer +// +// Notes: +// We really should consolidate the two abstractions for "stack frame identifiers" +// (StackFrame and FramePointer) when we move the debugger stackwalker to OOP. +// + +FORCEINLINE StackFrame ConvertFPToStackFrame(FramePointer fp) +{ + return StackFrame((UINT_PTR)fp.GetSPValue()); +} + +/* ------------------------------------------------------------------------- * + * DebuggerFrameInfo routines + * ------------------------------------------------------------------------- */ + +//struct DebuggerFrameData: Contains info used by the DebuggerWalkStackProc +// to do a stack walk. The info and pData fields are handed to the pCallback +// routine at each frame, +struct DebuggerFrameData +{ + // Initialize this struct. Only done at the start of a stackwalk. + void Init( + Thread * _pThread, + FramePointer _targetFP, + BOOL fIgnoreNonmethodFrames, // generally true for stackwalking and false for stepping + DebuggerStackCallback _pCallback, + void *_pData + ) + { + LIMITED_METHOD_CONTRACT; + + this->pCallback = _pCallback; + this->pData = _pData; + + this->cRealCounter = 0; + + this->thread = _pThread; + this->targetFP = _targetFP; + this->targetFound = (_targetFP == LEAF_MOST_FRAME); + + this->ignoreNonmethodFrames = fIgnoreNonmethodFrames; + + // For now, we can tie these to flags together. + // In everett, we disable SIS (For backwards compat). + this->fProvideInternalFrames = (fIgnoreNonmethodFrames != 0); + + this->fNeedToSendEnterManagedChain = false; + this->fTrackingUMChain = false; + this->fHitExitFrame = false; + + this->info.eStubFrameType = STUBFRAME_NONE; + this->info.quickUnwind = false; + + this->info.frame = NULL; + this->needParentInfo = false; + +#ifdef WIN64EXCEPTIONS + this->fpParent = LEAF_MOST_FRAME; + this->info.fIsLeaf = true; + this->info.fIsFunclet = false; + this->info.fIsFilter = false; +#endif // WIN64EXCEPTIONS + + // Look strange? Go to definition of this field. I dare you. + this->info.fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric = false; + +#if defined(_DEBUG) + this->previousFP = LEAF_MOST_FRAME; +#endif // _DEBUG + } + + // True if we need the next CrawlFrame to fill out part of this FrameInfo's data. + bool needParentInfo; + + // The FrameInfo that we'll dispatch to the pCallback. This matches against + // the CrawlFrame for that frame that the callback belongs too. + FrameInfo info; + + // Regdisplay that the EE stackwalker is updating. + REGDISPLAY regDisplay; + + +#ifdef WIN64EXCEPTIONS + // This is used to skip funclets in a stackwalk. It marks the frame pointer to which we should skip. + FramePointer fpParent; +#endif // WIN64EXCEPTIONS +#if defined(_DEBUG) + // For debugging, track the previous FramePointer so we can assert that we're + // making progress through the stack. + FramePointer previousFP; +#endif // _DEBUG + + // whether we have hit an exit frame or not (i.e. a M2U frame) + bool fHitExitFrame; + +private: + // The scope of this field is each section of managed method frames on the stack. + bool fNeedToSendEnterManagedChain; + + // Flag set when we first stack-walk to decide if we want to ignore certain frames. + // Stepping doesn't ignore these frames; end user stacktraces do. + BOOL ignoreNonmethodFrames; + + // Do we want callbacks for internal frames? + // Steppers generally don't. User stack-walk does. + bool fProvideInternalFrames; + + // Info for tracking unmanaged chains. + // We track the starting (leaf) context for an unmanaged chain, as well as the + // ending (root) framepointer. + bool fTrackingUMChain; + REGDISPLAY rdUMChainStart; + FramePointer fpUMChainEnd; + + // Thread that the stackwalk is for. + Thread *thread; + + + // Target FP indicates at what point in the stackwalk we'll start dispatching callbacks. + // Naturally, if this is LEAF_MOST_FRAME, then all callbacks will be dispatched + FramePointer targetFP; + bool targetFound; + + // Count # of callbacks we could have dispatched (assuming targetFP==LEAF_MOST_FRAME). + // Useful for detecting leaf. + int cRealCounter; + + // Callback & user-data supplied to that callback. + DebuggerStackCallback pCallback; + void *pData; + + private: + + // Raw invoke. This just does some consistency asserts, + // and invokes the callback if we're in the requested target range. + StackWalkAction RawInvokeCallback(FrameInfo * pInfo) + { +#ifdef _DEBUG + _ASSERTE(pInfo != NULL); + MethodDesc * md = pInfo->md; + // Invoke the callback to the user. Log what we're invoking. + LOG((LF_CORDB, LL_INFO10000, "DSWCallback: MD=%s,0x%p, Chain=%x, Stub=%x, Frame=0x%p, Internal=%d\n", + ((md == NULL) ? "None" : md->m_pszDebugMethodName), md, + pInfo->chainReason, + pInfo->eStubFrameType, + pInfo->frame, pInfo->internal)); + + // Make sure we're providing a valid FrameInfo for the callback. + pInfo->AssertValid(); +#endif + // Update counter. This provides a convenient check for leaf FrameInfo. + this->cRealCounter++; + + + // Only invoke if we're past the target. + if (!this->targetFound && IsEqualOrCloserToLeaf(this->targetFP, this->info.fp)) + { + this->targetFound = true; + } + + if (this->targetFound) + { + return (pCallback)(pInfo, pData); + } + else + { + LOG((LF_CORDB, LL_INFO10000, "Not invoking yet.\n")); + } + + return SWA_CONTINUE; + } + +public: + // Invoke a callback. This may do extra logic to preserve the interface between + // the LS stackwalker and the LS: + // - don't invoke if we're not at the target yet + // - send EnterManagedChains if we need it. + StackWalkAction InvokeCallback(FrameInfo * pInfo) + { + // Track if we've sent any managed code yet. + // If we haven't, then don't send the enter-managed chain. This catches cases + // when we have leaf-most unmanaged chain. + if ((pInfo->frame == NULL) && (pInfo->md != NULL)) + { + this->fNeedToSendEnterManagedChain = true; + } + + + // Do tracking to decide if we need to send a Enter-Managed chain. + if (pInfo->HasChainMarker()) + { + if (pInfo->managed) + { + // If we're dispatching a managed-chain, then we don't need to send another one. + fNeedToSendEnterManagedChain = false; + } + else + { + // If we're dispatching an UM chain, then send the Managed one. + // Note that the only unmanaged chains are ThreadStart chains and UM chains. + if (fNeedToSendEnterManagedChain) + { + fNeedToSendEnterManagedChain = false; + + FrameInfo f; + + // Assume entry chain's FP is one pointer-width after the upcoming UM chain. + FramePointer fpRoot = FramePointer::MakeFramePointer( + (BYTE*) GetRegdisplaySP(&pInfo->registers) - sizeof(DWORD*)); + + f.InitForEnterManagedChain(fpRoot); + if (RawInvokeCallback(&f) == SWA_ABORT) + { + return SWA_ABORT; + } + } + } + } + + return RawInvokeCallback(pInfo); + } + + // Note that we should start tracking an Unmanaged Chain. + void BeginTrackingUMChain(FramePointer fpRoot, REGDISPLAY * pRDSrc) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(!this->fTrackingUMChain); + + CopyREGDISPLAY(&this->rdUMChainStart, pRDSrc); + + this->fTrackingUMChain = true; + this->fpUMChainEnd = fpRoot; + this->fHitExitFrame = false; + + LOG((LF_CORDB, LL_EVERYTHING, "UM Chain starting at Frame=0x%p\n", this->fpUMChainEnd.GetSPValue())); + + // This UM chain may get cancelled later, so don't even worry about toggling the fNeedToSendEnterManagedChain bit here. + // Invoke() will track whether to send an Enter-Managed chain or not. + } + + // For various heuristics, we may not want to send an UM chain. + void CancelUMChain() + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(this->fTrackingUMChain); + this->fTrackingUMChain = false; + } + + // True iff we're currently tracking an unmanaged chain. + bool IsTrackingUMChain() + { + LIMITED_METHOD_CONTRACT; + + return this->fTrackingUMChain; + } + + + + // Get/Set Regdisplay that starts an Unmanaged chain. + REGDISPLAY * GetUMChainStartRD() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(fTrackingUMChain); + return &rdUMChainStart; + } + + // Get/Set FramePointer that ends an unmanaged chain. + void SetUMChainEnd(FramePointer fp) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(fTrackingUMChain); + fpUMChainEnd = fp; + } + + FramePointer GetUMChainEnd() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(fTrackingUMChain); + return fpUMChainEnd; + } + + // Get thread we're currently tracing. + Thread * GetThread() + { + LIMITED_METHOD_CONTRACT; + return thread; + } + + // Returns true if we're on the leaf-callback (ie, we haven't dispatched a callback yet. + bool IsLeafCallback() + { + LIMITED_METHOD_CONTRACT; + return cRealCounter == 0; + } + + bool ShouldProvideInternalFrames() + { + LIMITED_METHOD_CONTRACT; + return fProvideInternalFrames; + } + bool ShouldIgnoreNonmethodFrames() + { + LIMITED_METHOD_CONTRACT; + return ignoreNonmethodFrames != 0; + } +}; + + +//--------------------------------------------------------------------------------------- +// +// On IA64, the offset given by the OS during stackwalking is actually the offset at the call instruction. +// This is different from x86 and X64, where the offset is immediately after the call instruction. In order +// to have a uniform behaviour, we need to do adjust the relative offset on IA64. This function is a nop on +// other platforms. +// +// Arguments: +// pCF - the CrawlFrame for the current method frame +// pInfo - This is the FrameInfo for the current method frame. We need to use the fIsLeaf field, +// since no adjustment is necessary for leaf frames. +// +// Return Value: +// returns the adjusted relative offset +// + +inline ULONG AdjustRelOffset(CrawlFrame *pCF, + FrameInfo *pInfo) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(pCF != NULL); + } + CONTRACTL_END; + +#if defined(_TARGET_ARM_) + return pCF->GetRelOffset() & ~THUMB_CODE; +#else + return pCF->GetRelOffset(); +#endif +} + + +//--------------------------------------------------------------------------------------- +// +// Even when there is an exit frame in the explicit frame chain, it does not necessarily mean that we have +// actually called out to unmanaged code yet or that we actually have a managed call site. Given an exit +// frame, this function determines if we have a managed call site and have already called out to unmanaged +// code. If we have, then we return the caller SP as the potential frame pointer. Otherwise we return +// LEAF_MOST_FRAME. +// +// Arguments: +// pFrame - the exit frame to be checked +// pData - the state of the current frame maintained by the debugger stackwalker +// pPotentialFP - This is an out parameter. It returns the caller SP of the last managed caller if +// there is a managed call site and we have already called out to unmanaged code. +// Otherwise, LEAF_MOST_FRAME is returned. +// +// Return Value: +// true - we have a managed call site and we have called out to unmanaged code +// false - otherwise +// + +bool HasExitRuntime(Frame *pFrame, DebuggerFrameData *pData, FramePointer *pPotentialFP) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; // Callers demand this function be GC_NOTRIGGER. + MODE_ANY; + PRECONDITION(pFrame->GetFrameType() == Frame::TYPE_EXIT); + } + CONTRACTL_END; + +#ifdef _TARGET_X86_ + TADDR returnIP, returnSP; + + EX_TRY + { + // This is a real issue. This may be called while holding GC-forbid locks, and so + // this function can't trigger a GC. However, the only impl we have calls GC-trigger functions. + CONTRACT_VIOLATION(GCViolation); + pFrame->GetUnmanagedCallSite(NULL, &returnIP, &returnSP); + } + EX_CATCH + { + // We never expect an actual exception here (maybe in oom). + // If we get an exception, then simulate the default behavior for GetUnmanagedCallSite. + returnIP = NULL; + returnSP = NULL; // this will cause us to return true. + } + EX_END_CATCH(SwallowAllExceptions); + + LOG((LF_CORDB, LL_INFO100000, + "DWSP: TYPE_EXIT: returnIP=0x%08x, returnSP=0x%08x, frame=0x%08x, threadFrame=0x%08x, regSP=0x%08x\n", + returnIP, returnSP, pFrame, pData->GetThread()->GetFrame(), GetRegdisplaySP(&pData->regDisplay))); + + if (pPotentialFP != NULL) + { + *pPotentialFP = FramePointer::MakeFramePointer((void*)returnSP); + } + + return ((pFrame != pData->GetThread()->GetFrame()) || + (returnSP == NULL) || + ((TADDR)GetRegdisplaySP(&pData->regDisplay) <= returnSP)); + +#else // _TARGET_X86_ + // DebuggerExitFrame always return a NULL returnSP on x86. + if (pFrame->GetVTablePtr() == DebuggerExitFrame::GetMethodFrameVPtr()) + { + if (pPotentialFP != NULL) + { + *pPotentialFP = LEAF_MOST_FRAME; + } + return true; + } + else if (pFrame->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr()) + { + InlinedCallFrame *pInlinedFrame = static_cast<InlinedCallFrame *>(pFrame); + LPVOID sp = (LPVOID)pInlinedFrame->GetCallSiteSP(); + + // The sp returned below is the sp of the caller, which is either an IL stub in the normal case + // or a normal managed method in the inlined pinvoke case. + // This sp may be the same as the frame's address, so we need to use the largest + // possible bsp value to make sure that this frame pointer is closer to the root than + // the frame pointer made from the frame address itself. + if (pPotentialFP != NULL) + { + *pPotentialFP = FramePointer::MakeFramePointer( (LPVOID)sp ); + } + + return ((pFrame != pData->GetThread()->GetFrame()) || + InlinedCallFrame::FrameHasActiveCall(pInlinedFrame)); + + } + else + { + // It'll be nice if there's a way to assert that the current frame is indeed of a + // derived class of TransitionFrame. + TransitionFrame *pTransFrame = static_cast<TransitionFrame*>(pFrame); + LPVOID sp = (LPVOID)pTransFrame->GetSP(); + + // The sp returned below is the sp of the caller, which is either an IL stub in the normal case + // or a normal managed method in the inlined pinvoke case. + // This sp may be the same as the frame's address, so we need to use the largest + // possible bsp value to make sure that this frame pointer is closer to the root than + // the frame pointer made from the frame address itself. + if (pPotentialFP != NULL) + { + *pPotentialFP = FramePointer::MakeFramePointer( (LPVOID)sp ); + } + + return true; + } +#endif // _TARGET_X86_ +} + +#ifdef _DEBUG + +//----------------------------------------------------------------------------- +// Debug helpers to get name of Frame. +//----------------------------------------------------------------------------- +LPCUTF8 FrameInfo::DbgGetClassName() +{ + return (md == NULL) ? ("None") : (md->m_pszDebugClassName); +} +LPCUTF8 FrameInfo::DbgGetMethodName() +{ + return (md == NULL) ? ("None") : (md->m_pszDebugMethodName); +} + + +//----------------------------------------------------------------------------- +// Debug helper to asserts invariants about a FrameInfo before we dispatch it. +//----------------------------------------------------------------------------- +void FrameInfo::AssertValid() +{ + LIMITED_METHOD_CONTRACT; + + bool fMethod = this->HasMethodFrame(); + bool fStub = this->HasStubFrame(); + bool fChain = this->HasChainMarker(); + + // Can't be both Stub & Chain + _ASSERTE(!fStub || !fChain); + + // Must be at least a Method, Stub or Chain or Internal + _ASSERTE(fMethod || fStub || fChain || this->internal); + + // Check Managed status is consistent + if (fMethod) + { + _ASSERTE(this->managed); // We only report managed methods + } + if (fChain) + { + if (!managed) + { + // Only certain chains can be unmanaged + _ASSERTE((this->chainReason == CHAIN_THREAD_START) || + (this->chainReason == CHAIN_ENTER_UNMANAGED)); + } + else + { + // UM chains can never be managed. + _ASSERTE((this->chainReason != CHAIN_ENTER_UNMANAGED)); + } + + } + + // FramePointer should be valid + _ASSERTE(this->fp != LEAF_MOST_FRAME); + _ASSERTE((this->fp != ROOT_MOST_FRAME) || (chainReason== CHAIN_THREAD_START) || (chainReason == CHAIN_ENTER_UNMANAGED)); + + // If we have a Method, then we need an AppDomain. + // (RS will need it to do lookup) + if (fMethod) + { + _ASSERTE(currentAppDomain != NULL); + _ASSERTE(managed); + // Stubs may have a method w/o any code (eg, PInvoke wrapper). + // @todo - Frame::TYPE_TP_METHOD_FRAME breaks this assert. Are there other cases too? + //_ASSERTE(fStub || (pIJM != NULL)); + } + + if (fStub) + { + // All stubs (except LightWeightFunctions) match up w/a Frame. + _ASSERTE(this->frame || (eStubFrameType == STUBFRAME_LIGHTWEIGHT_FUNCTION)); + } +} +#endif + +//----------------------------------------------------------------------------- +// Get the DJI associated w/ this frame. This is a convenience function. +// This is recommended over using MethodDescs because DJI's are version-aware. +//----------------------------------------------------------------------------- +DebuggerJitInfo * FrameInfo::GetJitInfoFromFrame() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Not all FrameInfo objects correspond to actual code. + if (HasChainMarker() || HasStubFrame() || (frame != NULL)) + { + return NULL; + } + + DebuggerJitInfo *ji = NULL; + + // @todo - we shouldn't need both a MD and an IP here. + EX_TRY + { + _ASSERTE(this->md != NULL); + ji = g_pDebugger->GetJitInfo(this->md, (const BYTE*)GetControlPC(&(this->registers))); + _ASSERTE(ji != NULL); + _ASSERTE(ji->m_fd == this->md); + } + EX_CATCH + { + ji = NULL; + } + EX_END_CATCH(SwallowAllExceptions); + + return ji; +} + +//----------------------------------------------------------------------------- +// Get the DMI associated w/ this frame. This is a convenience function. +// DMIs are 1:1 with the (token, module) pair. +//----------------------------------------------------------------------------- +DebuggerMethodInfo * FrameInfo::GetMethodInfoFromFrameOrThrow() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + MethodDesc * pDesc = this->md; + mdMethodDef token = pDesc-> GetMemberDef(); + Module * pRuntimeModule = pDesc->GetModule(); + + DebuggerMethodInfo *dmi = g_pDebugger->GetOrCreateMethodInfo(pRuntimeModule, token); + return dmi; +} + + +//----------------------------------------------------------------------------- +// Init a FrameInfo for a UM chain. +// We need a stackrange to give to an unmanaged debugger. +// pRDSrc->Esp will provide the start (leaf) marker. +// fpRoot will provide the end (root) portion. +//----------------------------------------------------------------------------- +void FrameInfo::InitForUMChain(FramePointer fpRoot, REGDISPLAY * pRDSrc) +{ + _ASSERTE(pRDSrc != NULL); + + // Mark that we're an UM Chain (and nothing else). + this->frame = NULL; + this->md = NULL; + + // Fp will be the end (root) of the stack range. + // pRDSrc->Sp will be the start (leaf) of the stack range. + CopyREGDISPLAY(&(this->registers), pRDSrc); + this->fp = fpRoot; + + this->quickUnwind = false; + this->internal = false; + this->managed = false; + + // These parts of the FrameInfo can be ignored for a UM chain. + this->relOffset = 0; + this->pIJM = NULL; + this->MethodToken = METHODTOKEN(NULL, 0); + this->currentAppDomain = NULL; + this->exactGenericArgsToken = NULL; + + InitForScratchFrameInfo(); + + this->chainReason = CHAIN_ENTER_UNMANAGED; + this->eStubFrameType = STUBFRAME_NONE; + +#ifdef _DEBUG + FramePointer fpLeaf = GetSP(pRDSrc); + _ASSERTE(IsCloserToLeaf(fpLeaf, fpRoot)); +#endif + +#ifdef _DEBUG + // After we just init it, it had better be valid. + this->AssertValid(); +#endif +} + + +//--------------------------------------------------------------------------------------- +// +// This is just a small helper to initialize the fields which are specific to 64-bit. Note that you should +// only call this function on a scratch FrameInfo. Never call it on the FrameInfo used by the debugger +// stackwalker to store information on the current frame. +// + +void FrameInfo::InitForScratchFrameInfo() +{ +#ifdef WIN64EXCEPTIONS + // The following flags cannot be trashed when we are calling this function on the curret FrameInfo + // (the one we keep track of across multiple stackwalker callbacks). Thus, make sure you do not call + // this function from InitForDynamicMethod(). In all other cases, we can call this method after we + // call InitFromStubHelper() because we are working on a local scratch variable. + this->fIsLeaf = false; + this->fIsFunclet = false; + this->fIsFilter = false; +#endif // WIN64EXCEPTIONS +} + + +//----------------------------------------------------------------------------- +// +// Init a FrameInfo for a stub. Stub frames map to internal frames on the RS. Stubs which we care about +// usually contain an explicit frame which translates to an internal frame on the RS. Dynamic method is +// the sole exception. +// +// Arguments: +// pCF - the CrawlFrame containing the state of the current frame +// pMDHint - some stubs have associated MethodDesc but others don't, +// which is why this argument can be NULL +// type - the type of the stub/internal frame +// + +void FrameInfo::InitFromStubHelper( + CrawlFrame * pCF, + MethodDesc * pMDHint, // NULL ok + CorDebugInternalFrameType type +) +{ + _ASSERTE(pCF != NULL); + + Frame * pFrame = pCF->GetFrame(); + + LOG((LF_CORDB, LL_EVERYTHING, "InitFromStubHelper. Frame=0x%p, type=%d\n", pFrame, type)); + + // All Stubs have a Frame except for LightWeight methods + _ASSERTE((type == STUBFRAME_LIGHTWEIGHT_FUNCTION) || (pFrame != NULL)); + REGDISPLAY *pRDSrc = pCF->GetRegisterSet(); + + this->frame = pFrame; + + // Stub frames may be associated w/ a Method (as a hint). However this method + // will never have a JitManager b/c it will never have IL (if it had IL, we'd be a + // regulare frame, not a stub frame) + this->md = pMDHint; + + CopyREGDISPLAY(&this->registers, pRDSrc); + + // FramePointer must match up w/ an EE Frame b/c that's how we match + // we Exception callbacks. + if (pFrame != NULL) + { + this->fp = FramePointer::MakeFramePointer( + (LPVOID) pFrame); + } + else + { + this->fp = GetSP(pRDSrc); + } + + this->quickUnwind = false; + this->internal = false; + this->managed = true; + this->relOffset = 0; + this->ambientSP = NULL; + + + // Method associated w/a stub will never have a JitManager. + this->pIJM = NULL; + this->MethodToken = METHODTOKEN(NULL, 0); + this->currentAppDomain = pCF->GetAppDomain(); + this->exactGenericArgsToken = NULL; + + // Stub frames are mutually exclusive with chain markers. + this->chainReason = CHAIN_NONE; + this->eStubFrameType = type; + +#ifdef _DEBUG + // After we just init it, it had better be valid. + this->AssertValid(); +#endif +} + +//----------------------------------------------------------------------------- +// Initialize a FrameInfo to be used for an "InternalFrame" +// Frame should be a derived class of FramedMethodFrame. +// FrameInfo's MethodDesc will be for managed wrapper for native call. +//----------------------------------------------------------------------------- +void FrameInfo::InitForM2UInternalFrame(CrawlFrame * pCF) +{ + // For a M2U call, there's a managed method wrapping the unmanaged call. Use that. + Frame * pFrame = pCF->GetFrame(); + _ASSERTE(pFrame->GetTransitionType() == Frame::TT_M2U); + FramedMethodFrame * pM2U = static_cast<FramedMethodFrame*> (pFrame); + MethodDesc * pMDWrapper = pM2U->GetFunction(); + + // Soem M2U transitions may not have a function associated w/ them, + // so pMDWrapper may be NULL. PInvokeCalliFrame is an example. + + InitFromStubHelper(pCF, pMDWrapper, STUBFRAME_M2U); + InitForScratchFrameInfo(); +} + +//----------------------------------------------------------------------------- +// Initialize for the U2M case... +//----------------------------------------------------------------------------- +void FrameInfo::InitForU2MInternalFrame(CrawlFrame * pCF) +{ + PREFIX_ASSUME(pCF != NULL); + MethodDesc * pMDHint = NULL; + +#ifdef FEATURE_COMINTEROP + Frame * pFrame = pCF->GetFrame(); + PREFIX_ASSUME(pFrame != NULL); + + + // For regular U2M PInvoke cases, we don't care about MD b/c it's just going to + // be the next frame. + // If we're a COM2CLR call, perhaps we can get the MD for the interface. + if (pFrame->GetVTablePtr() == ComMethodFrame::GetMethodFrameVPtr()) + { + ComMethodFrame* pCOMFrame = static_cast<ComMethodFrame*> (pFrame); + ComCallMethodDesc* pCMD = reinterpret_cast<ComCallMethodDesc *> (pCOMFrame->ComMethodFrame::GetDatum()); + pMDHint = pCMD->GetInterfaceMethodDesc(); + + // Some COM-interop cases don't have an intermediate interface method desc, so + // pMDHint may be null. + } +#endif + + InitFromStubHelper(pCF, pMDHint, STUBFRAME_U2M); + InitForScratchFrameInfo(); +} + +//----------------------------------------------------------------------------- +// Init for an AD transition +//----------------------------------------------------------------------------- +void FrameInfo::InitForADTransition(CrawlFrame * pCF) +{ + Frame * pFrame; + pFrame = pCF->GetFrame(); + _ASSERTE(pFrame->GetTransitionType() == Frame::TT_AppDomain); + MethodDesc * pMDWrapper = NULL; + + InitFromStubHelper(pCF, pMDWrapper, STUBFRAME_APPDOMAIN_TRANSITION); + InitForScratchFrameInfo(); +} + + +//----------------------------------------------------------------------------- +// Init frame for a dynamic method. +//----------------------------------------------------------------------------- +void FrameInfo::InitForDynamicMethod(CrawlFrame * pCF) +{ + // These are just stack markers that there's a dynamic method on the callstack. + InitFromStubHelper(pCF, NULL, STUBFRAME_LIGHTWEIGHT_FUNCTION); + // Do not call InitForScratchFrameInfo() here! Please refer to the comment in that function. +} + +//----------------------------------------------------------------------------- +// Init an internal frame to mark a func-eval. +//----------------------------------------------------------------------------- +void FrameInfo::InitForFuncEval(CrawlFrame * pCF) +{ + // We don't store a MethodDesc hint referring to the method we're going to invoke because + // uses of stub frames will assume the MD is relative to the AppDomain the frame is in. + // For cross-AD funcevals, we're invoking a method in a domain other than the one this frame + // is in. + MethodDesc * pMDHint = NULL; + + // Add a stub frame here to mark that there is a FuncEvalFrame on the stack. + InitFromStubHelper(pCF, pMDHint, STUBFRAME_FUNC_EVAL); + InitForScratchFrameInfo(); +} + + +//--------------------------------------------------------------------------------------- +// +// Initialize a FrameInfo for sending the CHAIN_THREAD_START reason. +// The common case is that the chain is NOT managed, since the lowest (closest to the root) managed method +// is usually called from unmanaged code. In fact, in Whidbey, we should never have a managed chain. +// +// Arguments: +// pRDSrc - a REGDISPLAY for the beginning (the leafmost frame) of the chain +// +void FrameInfo::InitForThreadStart(Thread * pThread, REGDISPLAY * pRDSrc) +{ + this->frame = (Frame *) FRAME_TOP; + this->md = NULL; + CopyREGDISPLAY(&(this->registers), pRDSrc); + this->fp = FramePointer::MakeFramePointer(pThread->GetCachedStackBase()); + this->quickUnwind = false; + this->internal = false; + this->managed = false; + this->relOffset = 0; + this->pIJM = NULL; + this->MethodToken = METHODTOKEN(NULL, 0); + + this->currentAppDomain = NULL; + this->exactGenericArgsToken = NULL; + + InitForScratchFrameInfo(); + + this->chainReason = CHAIN_THREAD_START; + this->eStubFrameType = STUBFRAME_NONE; + +#ifdef _DEBUG + // After we just init it, it had better be valid. + this->AssertValid(); +#endif +} + + +//--------------------------------------------------------------------------------------- +// +// Initialize a FrameInfo for sending a CHAIN_ENTER_MANAGED. +// A Enter-Managed chain is always sent immediately before an UM chain, meaning that the Enter-Managed chain +// is closer to the leaf than the UM chain. +// +// Arguments: +// fpRoot - This is the frame pointer for the Enter-Managed chain. It is currently arbitrarily set +// to be one stack slot higher (closer to the leaf) than the frame pointer of the beginning +// of the upcoming UM chain. +// + +void FrameInfo::InitForEnterManagedChain(FramePointer fpRoot) +{ + // Nobody should use a EnterManagedChain's Frame*, but there's no + // good value to enforce that. + this->frame = (Frame *) FRAME_TOP; + this->md = NULL; + memset((void *)&this->registers, 0, sizeof(this->registers)); + this->fp = fpRoot; + + this->quickUnwind = true; + this->internal = false; + this->managed = true; + this->relOffset = 0; + this->pIJM = NULL; + this->MethodToken = METHODTOKEN(NULL, 0); + + this->currentAppDomain = NULL; + this->exactGenericArgsToken = NULL; + + InitForScratchFrameInfo(); + + this->chainReason = CHAIN_ENTER_MANAGED; + this->eStubFrameType = STUBFRAME_NONE; +} + +//----------------------------------------------------------------------------- +// Do tracking for UM chains. +// This may invoke the UMChain callback and M2U callback. +//----------------------------------------------------------------------------- +StackWalkAction TrackUMChain(CrawlFrame *pCF, DebuggerFrameData *d) +{ + Frame *frame = g_pEEInterface->GetFrame(pCF); + + // If we encounter an ExitFrame out in the wild, then we'll convert it to an UM chain. + if (!d->IsTrackingUMChain()) + { + if ((frame != NULL) && (frame != FRAME_TOP) && (frame->GetFrameType() == Frame::TYPE_EXIT)) + { + LOG((LF_CORDB, LL_EVERYTHING, "DWSP. ExitFrame while not tracking\n")); + REGDISPLAY* pRDSrc = pCF->GetRegisterSet(); + + d->BeginTrackingUMChain(GetSP(pRDSrc), pRDSrc); + + // fall through and we'll send the UM chain. + } + else + { + return SWA_CONTINUE; + } + } + + _ASSERTE(d->IsTrackingUMChain()); + + + // If we're tracking an UM chain, then we need to: + // - possibly refine the start & end values as we get new information in the stacktrace. + // - possibly cancel the UM chain for various heuristics. + // - possibly dispatch if we've hit managed code again. + + bool fDispatchUMChain = false; + // UM Chain stops when managed code starts again. + if (frame != NULL) + { + // If it's just a EE Frame, then update this as a possible end of stack range for the UM chain. + // (The end of a stack range is closer to the root.) + d->SetUMChainEnd(FramePointer::MakeFramePointer((LPVOID)(frame))); + + + Frame::ETransitionType t = frame->GetTransitionType(); + int ft = frame->GetFrameType(); + + + // Sometimes we may not want to show an UM chain b/c we know it's just + // code inside of mscorwks. (Eg: Funcevals & AD transitions both fall into this category). + // These are perfectly valid UM chains and we could give them if we wanted to. + if ((t == Frame::TT_AppDomain) || (ft == Frame::TYPE_FUNC_EVAL)) + { + d->CancelUMChain(); + return SWA_CONTINUE; + } + + // If we hit an M2U frame, then go ahead and dispatch the UM chain now. + // This will likely also be an exit frame. + if (t == Frame::TT_M2U) + { + fDispatchUMChain = true; + } + + // If we get an Exit frame, we can use that to "prune" the UM chain to a more friendly state. + // This heuristic is optional, it just eliminates lots of internal mscorwks frames from the callstack. + // Note that this heuristic is only useful if we get a callback on the entry frame + // (e.g. UMThkCallFrame) between the callback on the native marker and the callback on the exit frame. + // Otherwise the REGDISPLAY will be the same. + if (ft == Frame::TYPE_EXIT) + { + // If we have a valid reg-display (non-null IP) then update it. + // We may have an invalid reg-display if we have an exit frame on an inactive thread. + REGDISPLAY * pNewRD = pCF->GetRegisterSet(); + if (GetControlPC(pNewRD) != NULL) + { + LOG((LF_CORDB, LL_EVERYTHING, "DWSP. updating RD while tracking UM chain\n")); + CopyREGDISPLAY(d->GetUMChainStartRD(), pNewRD); + } + + FramePointer fpLeaf = GetSP(d->GetUMChainStartRD()); + _ASSERTE(IsCloserToLeaf(fpLeaf, d->GetUMChainEnd())); + + + _ASSERTE(!d->fHitExitFrame); // should only have 1 exit frame per UM chain code. + d->fHitExitFrame = true; + + FramePointer potentialFP; + + FramePointer fpNewChainEnd = d->GetUMChainEnd(); + + // Check to see if we are inside the unmanaged call. We want to make sure we only report an exit frame after + // we've really exited. There is a short period between where we setup the frame and when we actually exit + // the runtime. This check is intended to ensure we're actually outside now. + if (HasExitRuntime(frame, d, &potentialFP)) + { + LOG((LF_CORDB, LL_EVERYTHING, "HasExitRuntime. potentialFP=0x%p\n", potentialFP.GetSPValue())); + + // If we have no call site, manufacture a FP using the current frame. + // If we do have a call site, then the FP is actually going to be the caller SP, + // where the caller is the last managed method before calling out to unmanaged code. + if (potentialFP == LEAF_MOST_FRAME) + { + fpNewChainEnd = FramePointer::MakeFramePointer((LPVOID)((BYTE*)frame - sizeof(LPVOID))); + } + else + { + fpNewChainEnd = potentialFP; + } + + } + // For IL stubs, we may actually push an uninitialized InlinedCallFrame frame onto the frame chain + // in jitted managed code, and then later on initialize it in a native runtime helper. In this case, if + // HasExitRuntime() is false (meaning the frame is uninitialized), then we are actually still in managed + // code and have not made the call to native code yet, so we should report an unmanaged chain. + else + { + d->CancelUMChain(); + return SWA_CONTINUE; + } + + fDispatchUMChain = true; + + // If we got a valid chain end, then prune the UM chain accordingly. + // Note that some EE Frames will give invalid info back so we have to check. + // PInvokeCalliFrame is one example (when doing MC++ function pointers) + if (IsCloserToRoot(fpNewChainEnd, fpLeaf)) + { + d->SetUMChainEnd(fpNewChainEnd); + } + else + { + _ASSERTE(IsCloserToLeaf(fpLeaf, d->GetUMChainEnd())); + } + } // end ExitFrame + + // Only CLR internal code / stubs can push Frames onto the Frame chain. + // So if we hit a raw interceptor frame before we hit any managed frame, then this whole + // UM chain must still be in CLR internal code. + // Either way, this UM chain has ended (and some new chain based off the frame has started) + // so we need to either Cancel the chain or dispatch it. + if (frame->GetInterception() != Frame::INTERCEPTION_NONE) + { + // Interceptors may contain calls out to unmanaged code (such as unmanaged dllmain when + // loading a new dll), so we need to dispatch these. + // These extra UM chains don't show in Everett, and so everett debuggers on whidbey + // may see new chains. + // We need to ensure that whidbey debuggers are updated first. + fDispatchUMChain = true; + } + } + else + { + // If it's a real method (not just an EE Frame), then the UM chain is over. + fDispatchUMChain = true; + } + + + if (fDispatchUMChain) + { + // Check if we should cancel the UM chain. + + // We need to discriminate between the following 2 cases: + // 1) Managed -(a)-> mscorwks -(b)-> Managed (leaf) + // 2) Native -(a)-> mscorwks -(b)-> Managed (leaf) + // + // --INCORRECT RATIONALE SEE "CORRECTION" BELOW-- + // Case 1 could happen if a managed call injects a stub (such as w/ delegates). + // In both cases, the (mscorwks-(b)->managed) transition causes a IsNativeMarker callback + // which initiates a UM chain. In case 1, we want to cancel the UM chain, but + // in case 2 we want to dispatch it. + // The difference is case #2 will have some EE Frame at (b) and case #1 won't. + // That EE Frame should have caused us to dispatch the call for the managed method, and + // thus by the time we get around to dispatching the UM Chain, we shouldn't have a managed + // method waiting to be dispatched in the DebuggerFrameData. + // --END INCORRECT RATIONALE-- + // + // This is kind of messed up. First of all, the assertions on case 2 is not true on 64-bit. + // We won't have an explicit frame at (b). Secondly, case 1 is not always true either. + // Consider the case where we are calling a cctor at prestub time. This is what the stack may + // look like: managed -> PrestubMethodFrame -> GCFrame -> managed (cctor) (leaf). In this case, + // we will actually send the UM chain because we will have dispatched the call for the managed + // method (the cctor) when we get a callback for the GCFrame. + // + // --INCORRECT SEE "CORRECTION" BELOW-- + // Keep in mind that this is just a heuristic to reduce the number of UM chains we are sending + // over to the RS. + // --END INCORRECT -- + // + // CORRECTION: These UM chains also feed into the results of at least ControllerStackInfo and probably other + // places. Issue 650903 is a concrete example of how not filtering a UM chain causes correctness + // issues in the LS. This code may still have bugs in it based on those incorrect assumptions. + // A narrow fix for 650903 is the only thing that was changed at the time of adding this comment. + if (d->needParentInfo && d->info.HasMethodFrame()) + { + LOG((LF_CORDB, LL_EVERYTHING, "Cancelling UM Chain b/c it's internal\n")); + d->CancelUMChain(); + return SWA_CONTINUE; + } + + // If we're NOT ignoring non-method frames, and we didn't get an explicit ExitFrame somewhere + // in this chain, then don't send the non-leaf UM chain. + // The practical cause here is that w/o an exit frame, we don't know where the UM chain + // is starting (could be from anywhere in mscorwks). And we can't patch any random spot in + // mscorwks. + // Sending leaf-UM chains is OK b/c we can't step-out to them (they're the leaf, duh). + // (ignoreNonmethodFrames is generally false for stepping and true for regular + // end-user stacktraces.) + // + // This check is probably unnecessary. The client of the debugger stackwalker should make + // the decision themselves as to what to do with the UM chain callbacks. + // + // -- INCORRECT SEE SEE "CORRECTION" BELOW -- + // Currently, both + // ControllerStackInfo and InterceptorStackInfo ignore UM chains completely anyway. + // (For an example, refer to the cctor example in the previous comment.) + // -- END INCORRECT -- + // + // CORRECTION: See issue 650903 for a concrete example of ControllerStackInfo getting a different + // result based on a UM chain that wasn't filtered. This code may still have issues in + // it based on those incorrect assumptions. A narrow fix for 650903 is the only thing + // that was changed at the time of adding this comment. + if (!d->fHitExitFrame && !d->ShouldIgnoreNonmethodFrames() && !d->IsLeafCallback()) + { + LOG((LF_CORDB, LL_EVERYTHING, "Cancelling UM Chain b/c it's stepper not requested\n")); + d->CancelUMChain(); + return SWA_CONTINUE; + } + + + // Ok, we haven't cancelled it yet, so go ahead and send the UM chain. + FrameInfo f; + FramePointer fpRoot = d->GetUMChainEnd(); + FramePointer fpLeaf = GetSP(d->GetUMChainStartRD()); + + // If we didn't actually get any range, then don't bother sending it. + if (fpRoot == fpLeaf) + { + d->CancelUMChain(); + return SWA_CONTINUE; + } + + f.InitForUMChain(fpRoot, d->GetUMChainStartRD()); + +#ifdef FEATURE_COMINTEROP + if ((frame != NULL) && + (frame->GetVTablePtr() == ComPlusMethodFrame::GetMethodFrameVPtr())) + { + // This condition is part of the fix for 650903. (See + // code:ControllerStackInfo::WalkStack and code:DebuggerStepper::TrapStepOut + // for the other parts.) Here, we know that the frame we're looking it may be + // a ComPlusMethodFrameGeneric (this info is not otherwise plubmed down into + // the walker; even though the walker does get to see "f.frame", that may not + // be "frame"). Given this, if the walker chooses to ignore these frames + // (while doing a Step Out during managed-only debugging), then it can ignore + // this frame. + f.fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric = true; + } +#endif // FEATURE_COMINTEROP + + if (d->InvokeCallback(&f) == SWA_ABORT) + { + // don't need to cancel if they abort. + return SWA_ABORT; + } + d->CancelUMChain(); // now that we've sent it, we're done. + + + // Check for a M2U internal frame. + if (d->ShouldProvideInternalFrames() && (frame != NULL) && (frame != FRAME_TOP)) + { + // We want to dispatch a M2U transition right after we dispatch the UM chain. + Frame::ETransitionType t = frame->GetTransitionType(); + if (t == Frame::TT_M2U) + { + // Frame for a M2U transition. + FrameInfo fM2U; + fM2U.InitForM2UInternalFrame(pCF); + if (d->InvokeCallback(&fM2U) == SWA_ABORT) + { + return SWA_ABORT; + } + } + } + + + } + + return SWA_CONTINUE; +} + +//--------------------------------------------------------------------------------------- +// +// A frame pointer is a unique identifier for a particular stack location. This function returns the +// frame pointer for the current frame, whether it is a method frame or an explicit frame. +// +// Arguments: +// pData - the state of the current frame maintained by the debugger stackwalker +// pCF - the CrawlFrame for the current callback by the real stackwalker (i.e. StackWalkFramesEx()); +// this is NULL for the case where we fake an extra callbakc to top off a debugger stackwalk +// +// Return Value: +// the frame pointer for the current frame +// + +FramePointer GetFramePointerForDebugger(DebuggerFrameData* pData, CrawlFrame* pCF) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + FramePointer fpResult; + +#if defined(WIN64EXCEPTIONS) + if (pData->info.frame == NULL) + { + // This is a managed method frame. + fpResult = FramePointer::MakeFramePointer((LPVOID)GetRegdisplayStackMark(&pData->info.registers)); + } + else + { + // This is an actual frame. + fpResult = FramePointer::MakeFramePointer((LPVOID)(pData->info.frame)); + } + +#else // !WIN64EXCEPTIONS + if ((pCF == NULL || !pCF->IsFrameless()) && pData->info.frame != NULL) + { + // + // If we're in an explicit frame now, and the previous frame was + // also an explicit frame, pPC will not have been updated. So + // use the address of the frame itself as fp. + // + fpResult = FramePointer::MakeFramePointer((LPVOID)(pData->info.frame)); + + LOG((LF_CORDB, LL_INFO100000, "GFPFD: Two explicit frames in a row; using frame address 0x%p\n", + pData->info.frame)); + } + else + { + // + // Otherwise use pPC as the frame pointer, as this will be + // pointing to the return address on the stack. + // + fpResult = FramePointer::MakeFramePointer((LPVOID)GetRegdisplayStackMark(&(pData->regDisplay))); + } + +#endif // !WIN64EXCEPTIONS + + LOG((LF_CORDB, LL_INFO100000, "GFPFD: Frame pointer is 0x%p\n", fpResult.GetSPValue())); + + return fpResult; +} + + +#ifdef WIN64EXCEPTIONS +//--------------------------------------------------------------------------------------- +// +// This function is called to determine if we should start skipping funclets. If we should, then we return the +// frame pointer for the parent method frame. Otherwise we return LEAF_MOST_FRAME. If we are already skipping +// frames, then we return the current frame pointer for the parent method frame. +// +// The return value of this function corresponds to the return value of ExceptionTracker::FindParentStackFrame(). +// Refer to that function for more information. +// +// Arguments: +// fpCurrentParentMarker - This is the current frame pointer of the parent method frame. It can be +// LEAF_MOST_FRAME if we are not currently skipping funclets. +// pCF - the CrawlFrame for the current callback from the real stackwalker +// fIsNonFilterFuncletFrame - whether the current frame is a non-filter funclet frame +// +// Return Value: +// LEAF_MOST_FRAME - skipping not required +// ROOT_MOST_FRAME - skip one frame and try again +// anything else - skip all frames up to but not including the returned frame pointer +// + +inline FramePointer CheckForParentFP(FramePointer fpCurrentParentMarker, CrawlFrame* pCF, bool fIsNonFilterFuncletFrame) +{ + WRAPPER_NO_CONTRACT; + + if (fpCurrentParentMarker == LEAF_MOST_FRAME) + { + // When we encounter a funclet, we simply stop processing frames until we hit the parent + // of the funclet. Funclets and their parents have the same MethodDesc pointers, and they + // should really be treated as one frame. However, we report both of them and let the callers + // decide what they want to do with them. For example, DebuggerThread::TraceAndSendStack() + // should never report both frames, but ControllerStackInfo::GetStackInfo() may need both to + // determine where to put a patch. We use the fpParent as a flag to indicate if we are + // searching for a parent of a funclet. + // + // Note that filter funclets are an exception. We don't skip them. + if (fIsNonFilterFuncletFrame) + { + // We really should be using the same structure, but FramePointer is used everywhere in the debugger...... + StackFrame sfParent = g_pEEInterface->FindParentStackFrame(pCF); + return FramePointer::MakeFramePointer((LPVOID)sfParent.SP); + } + else + { + return LEAF_MOST_FRAME; + } + } + else + { + // Just return the current marker if we are already skipping frames. + return fpCurrentParentMarker; + } +} +#endif // WIN64EXCEPTIONS + + +//----------------------------------------------------------------------------- +// StackWalkAction DebuggerWalkStackProc(): This is the callback called +// by the EE stackwalker. +// Note that since we don't know what the frame pointer for frame +// X is until we've looked at the caller of frame X, we actually end up +// stashing the info and pData pointers in the DebuggerFrameDat struct, and +// then invoking pCallback when we've moved up one level, into the caller's +// frame. We use the needParentInfo field to indicate that the previous frame +// needed this (parental) info, and so when it's true we should invoke +// pCallback. +// What happens is this: if the previous frame set needParentInfo, then we +// do pCallback (and set needParentInfo to false). +// Then we look at the current frame - if it's frameless (ie, +// managed), then we set needParentInfo to callback in the next frame. +// Otherwise we must be at a chain boundary, and so we set the chain reason +// appropriately. We then figure out what type of frame it is, setting +// flags depending on the type. If the user should see this frame, then +// we'll set needParentInfo to record it's existence. Lastly, if we're in +// a funky frame, we'll explicitly update the register set, since the +// CrawlFrame doesn't do it automatically. +//----------------------------------------------------------------------------- +StackWalkAction DebuggerWalkStackProc(CrawlFrame *pCF, void *data) +{ + DebuggerFrameData *d = (DebuggerFrameData *)data; + + if (pCF->IsNativeMarker()) + { +#ifdef WIN64EXCEPTIONS + // The tricky part here is that we want to skip all frames between a funclet method frame + // and the parent method frame UNLESS the funclet is a filter. Moreover, we should never + // let a native marker execute the rest of this method, so we just short-circuit it here. + if ((d->fpParent != LEAF_MOST_FRAME) || d->info.IsNonFilterFuncletFrame()) + { + return SWA_CONTINUE; + } +#endif // WIN64EXCEPTIONS + + // This REGDISPLAY is for the native method immediately following the managed method for which + // we have received the previous callback, i.e. the native caller of the last managed method + // we have encountered. + REGDISPLAY* pRDSrc = pCF->GetRegisterSet(); + d->BeginTrackingUMChain(GetSP(pRDSrc), pRDSrc); + + return SWA_CONTINUE; + } + + // Note that a CrawlFrame may have both a methoddesc & an EE Frame. + Frame *frame = g_pEEInterface->GetFrame(pCF); + MethodDesc *md = pCF->GetFunction(); + + LOG((LF_CORDB, LL_EVERYTHING, "Calling DebuggerWalkStackProc. Frame=0x%p, md=0x%p(%s), native_marker=%d\n", + frame, md, (md == NULL || md == (MethodDesc*)POISONC) ? "null" : md->m_pszDebugMethodName, pCF->IsNativeMarker() )); + + // The fp for a frame must be obtained from the _next_ frame. Fill it in now for the previous frame, if appropriate. + if (d->needParentInfo) + { + LOG((LF_CORDB, LL_INFO100000, "DWSP: NeedParentInfo.\n")); + + d->info.fp = GetFramePointerForDebugger(d, pCF); + +#if defined(_DEBUG) && !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_) + // Make sure the stackwalk is making progress. + // On ARM this is invalid as the stack pointer does necessarily have to move when unwinding a frame. + _ASSERTE(IsCloserToLeaf(d->previousFP, d->info.fp)); + + d->previousFP = d->info.fp; +#endif // _DEBUG && !_TARGET_ARM_ + + d->needParentInfo = false; + + { + // Don't invoke Stubs if we're not asking for internal frames. + bool fDoInvoke = true; + if (!d->ShouldProvideInternalFrames()) + { + if (d->info.HasStubFrame()) + { + fDoInvoke = false; + } + } + + LOG((LF_CORDB, LL_INFO1000000, "DWSP: handling our target\n")); + + if (fDoInvoke) + { + if (d->InvokeCallback(&d->info) == SWA_ABORT) + { + return SWA_ABORT; + } + } + + // @todo - eventually we should be initing our frame-infos properly + // and thus should be able to remove this. + d->info.eStubFrameType = STUBFRAME_NONE; + } + } // if (d->needParentInfo) + + +#ifdef WIN64EXCEPTIONS + // The tricky part here is that we want to skip all frames between a funclet method frame + // and the parent method frame UNLESS the funclet is a filter. We only have to check for fpParent + // here (instead of checking d->info.fIsFunclet and d->info.fIsFilter as well, as in the beginning of + // this method) is because at this point, fpParent is already set by the code above. + if (d->fpParent == LEAF_MOST_FRAME) +#endif // WIN64EXCEPTIONS + { + // Track the UM chain after we flush any managed goo from the last iteration. + if (TrackUMChain(pCF, d) == SWA_ABORT) + { + return SWA_ABORT; + } + } + + + // Track if we want to send a callback for this Frame / Method + bool use=false; + + // + // Examine the frame. + // + + // We assume that the stack walker is just updating the + // register display we passed in - assert it to be sure + _ASSERTE(pCF->GetRegisterSet() == &d->regDisplay); + +#ifdef WIN64EXCEPTIONS + Frame* pPrevFrame = d->info.frame; + + // Here we need to determine if we are in a non-leaf frame, in which case we want to adjust the relative offset. + // Also, we need to check if this frame has faulted (throws a native exception), since if it has, then it should be + // considered the leaf frame (and thus we don't need to update the relative offset). + if (pCF->IsActiveFrame() || pCF->HasFaulted()) + { + d->info.fIsLeaf = true; + } + else if ( (pPrevFrame != NULL) && + (pPrevFrame->GetFrameType() == Frame::TYPE_EXIT) && + !HasExitRuntime(pPrevFrame, d, NULL) ) + { + // This is for the inlined NDirectMethodFrameGeneric case. We have not exit the runtime yet, so the current + // frame should still be regarded as the leaf frame. + d->info.fIsLeaf = true; + } + else + { + d->info.fIsLeaf = false; + } + + d->info.fIsFunclet = pCF->IsFunclet(); + d->info.fIsFilter = false; + if (d->info.fIsFunclet) + { + d->info.fIsFilter = pCF->IsFilterFunclet(); + } + + if (pCF->IsFrameless()) + { + // Check if we are skipping. + if (d->fpParent != LEAF_MOST_FRAME) + { + // If fpParent is ROOT_MOST_FRAME, then we just need to skip one frame. Otherwise, we should stop + // skipping if the current frame pointer matches fpParent. In either case, clear fpParent, and + // then check again. + if ((d->fpParent == ROOT_MOST_FRAME) || + ExceptionTracker::IsUnwoundToTargetParentFrame(pCF, ConvertFPToStackFrame(d->fpParent))) + { + LOG((LF_CORDB, LL_INFO100000, "DWSP: Stopping to skip funclet at 0x%p.\n", d->fpParent.GetSPValue())); + + d->fpParent = LEAF_MOST_FRAME; + d->fpParent = CheckForParentFP(d->fpParent, pCF, d->info.IsNonFilterFuncletFrame()); + } + } + } + +#endif // WIN64EXCEPTIONS + + d->info.frame = frame; + d->info.ambientSP = NULL; + + // Record the appdomain that the thread was in when it + // was running code for this frame. + d->info.currentAppDomain = pCF->GetAppDomain(); + + // Grab all the info from CrawlFrame that we need to + // check for "Am I in an exeption code blob?" now. + +#ifdef WIN64EXCEPTIONS + // We are still searching for the parent of the last funclet we encounter. + if (d->fpParent != LEAF_MOST_FRAME) + { + // We do nothing here. + LOG((LF_CORDB, LL_INFO100000, "DWSP: Skipping to parent method frame at 0x%p.\n", d->fpParent.GetSPValue())); + } + else +#endif // WIN64EXCEPTIONS + // We should ignore IL stubs with no frames in our stackwalking. + // The only exception is dynamic methods. We want to report them when SIS is turned on. + if ((md != NULL) && md->IsILStub() && pCF->IsFrameless()) + { +#ifdef FEATURE_STUBS_AS_IL + if(md->AsDynamicMethodDesc()->IsMulticastStub()) + { + use = true; + d->info.managed = true; + d->info.internal = false; + } +#endif + // We do nothing here. + LOG((LF_CORDB, LL_INFO100000, "DWSP: Skip frameless IL stub.\n")); + } + else + // For frames w/o method data, send them as an internal stub frame. + if ((md != NULL) && md->IsDynamicMethod()) + { + // Only Send the frame if "InternalFrames" are requested. + // Else completely ignore it. + if (d->ShouldProvideInternalFrames()) + { + d->info.InitForDynamicMethod(pCF); + + // We'll loop around to get the FramePointer. Only modification to FrameInfo + // after this is filling in framepointer and resetting MD. + use = true; + } + } + else if (pCF->IsFrameless()) + { + // Regular managed-method. + LOG((LF_CORDB, LL_INFO100000, "DWSP: Is frameless.\n")); + use = true; + d->info.managed = true; + d->info.internal = false; + d->info.chainReason = CHAIN_NONE; + d->needParentInfo = true; // Possibly need chain reason + d->info.relOffset = AdjustRelOffset(pCF, &(d->info)); + d->info.pIJM = pCF->GetJitManager(); + d->info.MethodToken = pCF->GetMethodToken(); + +#ifdef _TARGET_X86_ + // This is collecting the ambientSP a lot more than we actually need it. Only time we need it is + // inspecting local vars that are based off the ambient esp. + d->info.ambientSP = pCF->GetAmbientSPFromCrawlFrame(); +#endif + } + else + { + d->info.pIJM = NULL; + d->info.MethodToken = METHODTOKEN(NULL, 0); + + // + // Retrieve any interception info + // + + // Each interception type in the switch statement below is associated with a chain reason. + // The other chain reasons are: + // CHAIN_INTERCEPTION - not used + // CHAIN_PROCESS_START - not used + // CHAIN_THREAD_START - thread start + // CHAIN_ENTER_MANAGED - managed chain + // CHAIN_ENTER_UNMANAGED - unmanaged chain + // CHAIN_DEBUGGER_EVAL - not used + // CHAIN_CONTEXT_SWITCH - not used + // CHAIN_FUNC_EVAL - funceval + + switch (frame->GetInterception()) + { + case Frame::INTERCEPTION_CLASS_INIT: + // + // Fall through + // + + // V2 assumes that the only thing the prestub intercepts is the class constructor + case Frame::INTERCEPTION_PRESTUB: + d->info.chainReason = CHAIN_CLASS_INIT; + break; + + case Frame::INTERCEPTION_EXCEPTION: + d->info.chainReason = CHAIN_EXCEPTION_FILTER; + break; + + case Frame::INTERCEPTION_CONTEXT: + d->info.chainReason = CHAIN_CONTEXT_POLICY; + break; + + case Frame::INTERCEPTION_SECURITY: + d->info.chainReason = CHAIN_SECURITY; + break; + + default: + d->info.chainReason = CHAIN_NONE; + } + + // + // Look at the frame type to figure out how to treat it. + // + + LOG((LF_CORDB, LL_INFO100000, "DWSP: Chain reason is 0x%X.\n", d->info.chainReason)); + + switch (frame->GetFrameType()) + { + case Frame::TYPE_ENTRY: // We now ignore entry + exit frames. + case Frame::TYPE_EXIT: + case Frame::TYPE_HELPER_METHOD_FRAME: + case Frame::TYPE_INTERNAL: + + /* If we have a specific interception type, use it. However, if this + is the top-most frame (with a specific type), we can ignore it + and it wont appear in the stack-trace */ +#define INTERNAL_FRAME_ACTION(d, use) \ + (d)->info.managed = true; \ + (d)->info.internal = false; \ + use = true + + LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_INTERNAL.\n")); + if (d->info.chainReason == CHAIN_NONE || pCF->IsActiveFrame()) + { + use = false; + } + else + { + INTERNAL_FRAME_ACTION(d, use); + } + break; + + case Frame::TYPE_INTERCEPTION: + case Frame::TYPE_SECURITY: // Security is a sub-type of interception + LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_INTERCEPTION/TYPE_SECURITY.\n")); + d->info.managed = true; + d->info.internal = true; + use = true; + break; + + case Frame::TYPE_CALL: + LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_CALL.\n")); + // In V4, StubDispatchFrame is only used on 64-bit (and PPC?) but not on x86. x86 uses a + // different code path which sets up a HelperMethodFrame instead. In V4.5, x86 and ARM + // both use the 64-bit code path and they set up a StubDispatchFrame as well. This causes + // a problem in the debugger stackwalker (see Dev11 Issue 13229) since the two frame types + // are treated differently. More specifically, a StubDispatchFrame causes the debugger + // stackwalk to make an invalid callback, i.e. a callback which is not for a managed method, + // an explicit frame, or a chain. + // + // Ideally we would just change the StubDispatchFrame to behave like a HMF, but it's + // too big of a change for an in-place release. For now I'm just making surgical fixes in + // the debugger stackwalker. This may introduce behavioural changes in on X64, but the + // chance of that is really small. StubDispatchFrame is only used in the virtual stub + // disptch code path. It stays on the stack in a small time window and it's not likely to + // be on the stack while some managed methods closer to the leaf are on the stack. There is + // only one scenario I know of, and that's the repro for Dev11 13229, but that's for x86 only. + // The jitted code on X64 behaves differently. + // + // Note that there is a corresponding change in DacDbiInterfaceImpl::GetInternalFrameType(). + if (frame->GetVTablePtr() == StubDispatchFrame::GetMethodFrameVPtr()) + { + use = false; + } + else + { + d->info.managed = true; + d->info.internal = false; + use = true; + } + break; + + case Frame::TYPE_FUNC_EVAL: + LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_FUNC_EVAL.\n")); + d->info.managed = true; + d->info.internal = true; + // This is actually a nop. We reset the chain reason in InitForFuncEval() below. + // So is a FuncEvalFrame a chain or an internal frame? + d->info.chainReason = CHAIN_FUNC_EVAL; + + { + // We only show a FuncEvalFrame if the funceval is not trying to abort the thread. + FuncEvalFrame *pFuncEvalFrame = static_cast<FuncEvalFrame *>(frame); + use = pFuncEvalFrame->ShowFrame() ? true : false; + } + + // Send Internal frame. This is "inside" (leafmost) the chain, so we send it first + // since sending starts from the leaf. + if (use && d->ShouldProvideInternalFrames()) + { + FrameInfo f; + f.InitForFuncEval(pCF); + if (d->InvokeCallback(&f) == SWA_ABORT) + { + return SWA_ABORT; + } + } + + break; + + // Put frames we want to ignore here: + case Frame::TYPE_MULTICAST: + LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_MULTICAST.\n")); + if (d->ShouldIgnoreNonmethodFrames()) + { + // Multicast frames exist only to gc protect the arguments + // between invocations of a delegate. They don't have code that + // we can (currently) show the user (we could change this with + // work, but why bother? It's an internal stub, and even if the + // user could see it, they can't modify it). + LOG((LF_CORDB, LL_INFO100000, "DWSP: Skipping frame 0x%x b/c it's " + "a multicast frame!\n", frame)); + use = false; + } + else + { + LOG((LF_CORDB, LL_INFO100000, "DWSP: NOT Skipping frame 0x%x even thought it's " + "a multicast frame!\n", frame)); + INTERNAL_FRAME_ACTION(d, use); + } + break; + +#ifdef FEATURE_REMOTING + case Frame::TYPE_TP_METHOD_FRAME: + LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_TP_METHOD_FRAME.\n")); + if (d->ShouldIgnoreNonmethodFrames()) + { + // Transparant Proxies push a frame onto the stack that they + // use to figure out where they're really going; this frame + // doesn't actually contain any code, although it does have + // enough info into fooling our routines into thinking it does: + // Just ignore these. + LOG((LF_CORDB, LL_INFO100000, "DWSP: Skipping frame 0x%x b/c it's " + "a transparant proxy frame!\n", frame)); + use = false; + } + else + { + // Otherwise do the same thing as for internal frames + LOG((LF_CORDB, LL_INFO100000, "DWSP: NOT Skipping frame 0x%x even though it's " + "a transparant proxy frame!\n", frame)); + INTERNAL_FRAME_ACTION(d, use); + } + break; +#endif + default: + _ASSERTE(!"Invalid frame type!"); + break; + } + } + + + // Check for ICorDebugInternalFrame stuff. + // These callbacks are dispatched out of band. + if (d->ShouldProvideInternalFrames() && (frame != NULL) && (frame != FRAME_TOP)) + { + Frame::ETransitionType t = frame->GetTransitionType(); + FrameInfo f; + bool fUse = false; + + if (t == Frame::TT_U2M) + { + // We can invoke the Internal U2M frame now. + f.InitForU2MInternalFrame(pCF); + fUse = true; + } + else if (t == Frame::TT_AppDomain) + { + // Internal frame for an Appdomain transition. + // We used to ignore frames for ADs which we hadn't sent a Create event for yet. In V3 we send AppDomain + // create events immediately (before any assemblies are loaded), so this should no longer be an issue. + f.InitForADTransition(pCF); + fUse = true; + } + + // Frame's setup. Now invoke the callback. + if (fUse) + { + if (d->InvokeCallback(&f) == SWA_ABORT) + { + return SWA_ABORT; + } + } + } // should we give frames? + + + + if (use) + { + // + // If we are returning a complete stack walk from the helper thread, then we + // need to gather information to instantiate generics. However, a stepper doing + // a stackwalk does not need this information, so skip in that case. + // + if (d->ShouldIgnoreNonmethodFrames()) + { + // Finding sizes of value types on the argument stack while + // looking for the arg runs the class loader in non-load mode. + ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); + d->info.exactGenericArgsToken = pCF->GetExactGenericArgsToken(); + } + else + { + d->info.exactGenericArgsToken = NULL; + } + + d->info.md = md; + CopyREGDISPLAY(&(d->info.registers), &(d->regDisplay)); + +#if defined(_TARGET_AMD64_) + LOG((LF_CORDB, LL_INFO100000, "DWSP: Saving REGDISPLAY with sp = 0x%p, pc = 0x%p.\n", + GetRegdisplaySP(&(d->info.registers)), + GetControlPC(&(d->info.registers)))); +#endif // _TARGET_AMD64_ + + d->needParentInfo = true; + LOG((LF_CORDB, LL_INFO100000, "DWSP: Setting needParentInfo\n")); + } + +#if defined(WIN64EXCEPTIONS) + d->fpParent = CheckForParentFP(d->fpParent, pCF, d->info.IsNonFilterFuncletFrame()); +#endif // WIN64EXCEPTIONS + + // + // The stackwalker doesn't update the register set for the + // case where a non-frameless frame is returning to another + // non-frameless frame. Cover this case. + // + // !!! This assumes that updating the register set multiple times + // for a given frame times is not a bad thing... + // + if (!pCF->IsFrameless()) + { + LOG((LF_CORDB, LL_INFO100000, "DWSP: updating regdisplay.\n")); + pCF->GetFrame()->UpdateRegDisplay(&d->regDisplay); + } + + return SWA_CONTINUE; +} + +#if defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING) +// Helper to get the Wait-Sleep-Join bit from the thread +bool IsInWaitSleepJoin(Thread * pThread) +{ + // Partial User state is sufficient because that has the bit we're checking against. + CorDebugUserState cts = g_pEEInterface->GetPartialUserState(pThread); + return ((cts & USER_WAIT_SLEEP_JOIN) != 0); +} + +//----------------------------------------------------------------------------- +// Decide if we should send an UM leaf chain. +// This goes through a bunch of heuristics. +// The driving guidelines here are: +// - we try not to send an UM chain if it's just internal mscorwks stuff +// and we know it can't have native user code. +// (ex, anything beyond a filter context, various hijacks, etc). +// - If it may have native user code, we send it anyway. +//----------------------------------------------------------------------------- +bool ShouldSendUMLeafChain(Thread * pThread) +{ + // If we're in shutodown, don't bother trying to sniff for an UM leaf chain. + // @todo - we'd like to never even be trying to stack trace on shutdown, this + // comes up when we do helper thread duty on shutdown. + if (g_fProcessDetach) + { + return false; + } + + if (pThread->IsUnstarted() || pThread->IsDead()) + { + return false; + } + + // If a thread is suspended for sync purposes, it was suspended from managed + // code and the only native code is a mscorwks hijack. + // There are a few caveats here: + // - This means a thread will lose it's UM chain. But what if a user inactive thread + // enters the CLR from native code and hits a GC toggle? We'll lose that entire + // UM chain. + // - at a managed-only stop, preemptive threads are still live. Thus a thread + // may not have this state set, run a little, try to enter the GC, and then get + // this state set. Thus we'll lose the UM chain right out from under our noses. + Thread::ThreadState ts = pThread->GetSnapshotState(); + if ((ts & Thread::TS_SyncSuspended) != 0) + { + // If we've been stopped inside the runtime (eg, at a gc-toggle) but + // not actually at a stopping context, then the thread must have some + // leafframes in mscorwks. + // We can detect this case by checking if GetManagedStoppedCtx(pThread) == NULL. + // This is very significant for notifcations (like LogMessage) that are + // dispatches from within mscorwks w/o a filter context. + // We don't send a UM chain for these cases because that would + // cause managed debug events to be dispatched w/ UM chains on the callstack. + // And that just seems wrong ... + + return false; + } + +#ifdef FEATURE_HIJACK + if ((ts & Thread::TS_Hijacked) != 0) + { + return false; + } +#endif + + // This is pretty subjective. If we have a thread stopped in a managed sleep, + // managed wait, or managed join, then don't bother showing the native end of the + // stack. This check can be removed w/o impacting correctness. + // @todo - may be a problem if Sleep/Wait/Join go through a hosting interface + // which lands us in native user code. + // Partial User state is sufficient because that has the bit we're checking against. + if (IsInWaitSleepJoin(pThread)) + { + return false; + } + + // If we're tracing ourselves, we must be in managed code. + // Native user code can't initiate a managed stackwalk. + if (pThread == GetThread()) + { + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Prepare a Leaf UM chain. This assumes we should send an UM leaf chain. +// Returns true if we actually prep for an UM leaf, +// false if we don't. +//----------------------------------------------------------------------------- +bool PrepareLeafUMChain(DebuggerFrameData * pData, CONTEXT * pCtxTemp) +{ + // Get the current user context (depends on if we're the active thread or not). + Thread * thread = pData->GetThread(); + REGDISPLAY * pRDSrc = NULL; + REGDISPLAY rdTemp; + + +#ifdef _DEBUG + // Anybody stopped at an native debug event (and hijacked) should have a filter ctx. + if (thread->GetInteropDebuggingHijacked() && (thread->GetFrame() != NULL) && (thread->GetFrame() != FRAME_TOP)) + { + _ASSERTE(g_pEEInterface->GetThreadFilterContext(thread) != NULL); + } +#endif + + // If we're hijacked, then we assume we're in native code. This covers the active thread case. + if (g_pEEInterface->GetThreadFilterContext(thread) != NULL) + { + LOG((LF_CORDB, LL_EVERYTHING, "DWS - sending special case UM Chain.\n")); + + // This will get it from the filter ctx. + pRDSrc = &(pData->regDisplay); + } + else + { + // For inactive thread, we may not be hijacked. So just get the current ctx. + // This will use a filter ctx if we have one. + // We may suspend a thread in native code w/o hijacking it, so it's still at it's live context. + // This can happen when we get a debug event on 1 thread; and then switch to look at another thread. + // This is very common when debugging apps w/ cross-thread causality (including COM STA objects) + pRDSrc = &rdTemp; + + bool fOk; + + + // We need to get thread's context (InitRegDisplay will do that under the covers). + // If this is our thread, we're in bad shape. Fortunately that should never happen. + _ASSERTE(thread != GetThread()); + + Thread::SuspendThreadResult str = thread->SuspendThread(); + if (str != Thread::STR_Success) + { + return false; + } + + // @todo - this context is less important because the RS will overwrite it with the live context. + // We don't need to even bother getting it. We can just intialize the regdisplay w/ a sentinal. + fOk = g_pEEInterface->InitRegDisplay(thread, pRDSrc, pCtxTemp, false); + thread->ResumeThread(); + + if (!fOk) + { + return false; + } + } + + // By now we have a Regdisplay from somewhere (filter ctx, current ctx, etc). + _ASSERTE(pRDSrc != NULL); + + // If we're stopped in mscorwks (b/c of a handler for a managed BP), then the filter ctx will + // still be set out in jitted code. + // If our regdisplay is out in UM code , then send a UM chain. + BYTE* ip = (BYTE*) GetControlPC(pRDSrc); + if (g_pEEInterface->IsManagedNativeCode(ip)) + { + return false; + } + + LOG((LF_CORDB, LL_EVERYTHING, "DWS - sending leaf UM Chain.\n")); + + // Get the ending fp. We may not have any managed goo on the stack (eg, native thread called + // into a managed method and then returned from it). + FramePointer fpRoot; + Frame * pFrame = thread->GetFrame(); + if ((pFrame != NULL) && (pFrame != FRAME_TOP)) + { + fpRoot = FramePointer::MakeFramePointer((void*) pFrame); + } + else + { + fpRoot= ROOT_MOST_FRAME; + } + + + // Start tracking an UM chain. We won't actually send the UM chain until + // we hit managed code. Since this is the leaf, we don't need to send an + // Enter-Managed chain either. + pData->BeginTrackingUMChain(fpRoot, pRDSrc); + + return true; +} +#endif // defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING) + +//----------------------------------------------------------------------------- +// Entry function for the debugger's stackwalking layer. +// This will invoke pCallback(FrameInfo * pInfo, pData) for each 'frame' +//----------------------------------------------------------------------------- +StackWalkAction DebuggerWalkStack(Thread *thread, + FramePointer targetFP, + CONTEXT *context, + BOOL contextValid, + DebuggerStackCallback pCallback, + void *pData, + BOOL fIgnoreNonmethodFrames) +{ + _ASSERTE(context != NULL); + + DebuggerFrameData data; + + StackWalkAction result = SWA_CONTINUE; + bool fRegInit = false; + + LOG((LF_CORDB, LL_EVERYTHING, "DebuggerWalkStack called\n")); + + if(contextValid || g_pEEInterface->GetThreadFilterContext(thread) != NULL) + { + fRegInit = g_pEEInterface->InitRegDisplay(thread, &data.regDisplay, context, contextValid != 0); + _ASSERTE(fRegInit); + } + + if (!fRegInit) + { +#if defined(CONTEXT_EXTENDED_REGISTERS) + + // Note: the size of a CONTEXT record contains the extended registers, but the context pointer we're given + // here may not have room for them. Therefore, we only set the non-extended part of the context to 0. + memset((void *)context, 0, offsetof(CONTEXT, ExtendedRegisters)); +#else + memset((void *)context, 0, sizeof(CONTEXT)); +#endif + memset((void *)&data, 0, sizeof(data)); + +#if defined(_TARGET_X86_) + // @todo - this seems pointless. context->Eip will be 0; and when we copy it over to the DebuggerRD, + // the context will be completely null. + data.regDisplay.ControlPC = context->Eip; + data.regDisplay.PCTAddr = (TADDR)&(context->Eip); + +#else + // + // @TODO: this should be the code for all platforms now that it uses FillRegDisplay, + // which encapsulates the platform variances. This could all be avoided if we used + // StackWalkFrames instead of StackWalkFramesEx. + // + ::SetIP(context, 0); + ::SetSP(context, 0); + FillRegDisplay(&data.regDisplay, context); + + ::SetSP(data.regDisplay.pCallerContext, 0); +#endif + } + + data.Init(thread, targetFP, fIgnoreNonmethodFrames, pCallback, pData); + + +#if defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING) + CONTEXT ctxTemp; // Temp context for Leaf UM chain. Need it here so that it stays alive for whole stackwalk. + + // Important case for Interop Debugging - + // We may be stopped in Native Code (perhaps at a BP) w/ no Transition frame on the stack! + // We still need to send an UM Chain for this case. + if (ShouldSendUMLeafChain(thread)) + { + // It's possible this may fail (eg, GetContext fails on win9x), so we're not guaranteed + // to be sending an UM chain even though we want to. + PrepareLeafUMChain(&data, &ctxTemp); + + } +#endif // defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING) + + if ((result != SWA_FAILED) && !thread->IsUnstarted() && !thread->IsDead()) + { + int flags = 0; + + result = g_pEEInterface->StackWalkFramesEx(thread, &data.regDisplay, + DebuggerWalkStackProc, + &data, flags | HANDLESKIPPEDFRAMES | NOTIFY_ON_U2M_TRANSITIONS | ALLOW_ASYNC_STACK_WALK); + } + else + { + result = SWA_DONE; + } + + if (result == SWA_DONE || result == SWA_FAILED) // SWA_FAILED if no frames + { + // Since Debugger StackWalk callbacks are delayed 1 frame from EE stackwalk callbacks, we + // have to touch up the 1 leftover here. + // + // This is safe only because we use the REGDISPLAY of the native marker callback for any subsequent + // explicit frames which do not update the REGDISPLAY. It's kind of fragile. If we can change + // the x86 real stackwalker to unwind one frame ahead of time, we can get rid of this code. + if (data.needParentInfo) + { + data.info.fp = GetFramePointerForDebugger(&data, NULL); + + if (data.InvokeCallback(&data.info) == SWA_ABORT) + { + return SWA_ABORT; + } + } + + // + // Top off the stack trace as necessary w/ a thread-start chain. + // + REGDISPLAY * pRegDisplay = &(data.regDisplay); + if (data.IsTrackingUMChain()) + { + // This is the common case b/c managed code gets called from native code. + pRegDisplay = data.GetUMChainStartRD(); + } + + + // All Thread starts in unmanaged code (at something like kernel32!BaseThreadStart), + // so all ThreadStart chains must be unmanaged. + // InvokeCallback will fabricate the EnterManaged chain if we haven't already sent one. + data.info.InitForThreadStart(thread, pRegDisplay); + result = data.InvokeCallback(&data.info); + + } + return result; +} |