diff options
Diffstat (limited to 'src/debug/di/shimstackwalk.cpp')
-rw-r--r-- | src/debug/di/shimstackwalk.cpp | 2264 |
1 files changed, 2264 insertions, 0 deletions
diff --git a/src/debug/di/shimstackwalk.cpp b/src/debug/di/shimstackwalk.cpp new file mode 100644 index 0000000000..9be1ea1a78 --- /dev/null +++ b/src/debug/di/shimstackwalk.cpp @@ -0,0 +1,2264 @@ +// 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. +// +// ShimStackWalk.cpp +// + +// +// This file contains the implementation of the Arrowhead stackwalking shim. This shim builds on top of +// the public Arrowhead ICD stackwalking API, and it is intended to be backward-compatible with the existing +// debuggers using the V2.0 ICD API. +// +// ====================================================================================== + +#include "stdafx.h" +#include "primitives.h" + +#if defined(_TARGET_X86_) +static const ULONG32 REGISTER_X86_MAX = REGISTER_X86_FPSTACK_7 + 1; +static const ULONG32 MAX_MASK_COUNT = (REGISTER_X86_MAX + 7) >> 3; +#elif defined(_TARGET_AMD64_) +static const ULONG32 REGISTER_AMD64_MAX = REGISTER_AMD64_XMM15 + 1; +static const ULONG32 MAX_MASK_COUNT = (REGISTER_AMD64_MAX + 7) >> 3; +#endif + +ShimStackWalk::ShimStackWalk(ShimProcess * pProcess, ICorDebugThread * pThread) + : m_pChainEnumList(NULL), + m_pFrameEnumList(NULL) +{ + // The following assignments increment the ref count. + m_pProcess.Assign(pProcess); + m_pThread.Assign(pThread); + + Populate(); +} + +ShimStackWalk::~ShimStackWalk() +{ + Clear(); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::Clear +// +// Description: +// Clear all the memory used by this ShimStackWalk, including the array of frames, the array of chains, +// the linked list of ShimChainEnums, and the linked list of ShimFrameEnums. +// + +void ShimStackWalk::Clear() +{ + // call Release() on each of the ShimChains + for (int i = 0; i < m_stackChains.Count(); i++) + { + (*m_stackChains.Get(i))->Neuter(); + (*m_stackChains.Get(i))->Release(); + } + m_stackChains.Clear(); + + // call Release() on each of the ICDFrames + for (int i = 0; i < m_stackFrames.Count(); i++) + { + (*m_stackFrames.Get(i))->Release(); + } + m_stackFrames.Clear(); + + // call Release() on each of the ShimChainEnums + while (m_pChainEnumList != NULL) + { + ShimChainEnum * pCur = m_pChainEnumList; + m_pChainEnumList = m_pChainEnumList->GetNext(); + pCur->Neuter(); + pCur->Release(); + } + + // call Release() on each of the ShimFrameEnums + while (m_pFrameEnumList != NULL) + { + ShimFrameEnum * pCur = m_pFrameEnumList; + m_pFrameEnumList = m_pFrameEnumList->GetNext(); + pCur->Neuter(); + pCur->Release(); + } + + // release the references + m_pProcess.Clear(); + m_pThread.Clear(); +} + +//--------------------------------------------------------------------------------------- +// +// Helper used by the stackwalker to determine whether a given UM chain should be tracked +// during the stackwalk for eventual transmission to the debugger. This function is the +// V4 equivalent of Whidbey's code:ShouldSendUMLeafChain (which ran on the LS, from +// Debug\EE\frameinfo.cpp). +// +// Note that code:ShouldSendUMLeafChain still exists today (to facilitate some in-process +// debugging stackwalks that are still necessary). So consult the comments in +// code:ShouldSendUMLeafChain for a more thorough discussion of why we do the checks we +// do to decide whether to track the chain. +// +// Arguments: +// pswInfo - StackWalkInfo representing the frame in question +// +// Return Value: +// nonzero iff the chain should be tracked +// + +BOOL ShimStackWalk::ShouldTrackUMChain(StackWalkInfo * pswInfo) +{ + _ASSERTE (pswInfo != NULL); + + // Always track chains for non-leaf UM frames + if (!pswInfo->IsLeafFrame()) + return TRUE; + + // Sometimes we want to track leaf UM chains, and sometimes we don't. Check all the + // reasons not to track the chain, and return FALSE if any of them are hit. + + CorDebugUserState threadUserState; + HRESULT hr = m_pThread->GetUserState(&threadUserState); + IfFailThrow(hr); + + // ShouldSendUMLeafChain checked IsInWaitSleepJoin which is just USER_WAIT_SLEEP_JOIN + if ((threadUserState & USER_WAIT_SLEEP_JOIN) != 0) + return FALSE; + + // This check is the same as Thread::IsUnstarted() from ShouldSendUMLeafChain + if ((threadUserState & USER_UNSTARTED) != 0) + return FALSE; + + // This check is the same as Thread::IsDead() from ShouldSendUMLeafChain + if ((threadUserState & USER_STOPPED) != 0) + return FALSE; + + // #DacShimSwWorkAround + // + // This part cannot be determined using DBI alone. We must call through to the DAC + // because we have no other way to get at TS_Hijacked & TS_SyncSuspended. When the + // rearchitecture is complete, this DAC call should be able to go away, and we + // should be able to use DBI for all the info we need. + // + // One might think one could avoid the DAC for TS_SyncSuspended by just checking + // USER_SUSPENDED, but that won't work. Although USER_SUSPENDED will be returned some + // of the time when TS_SyncSuspended is set, that will not be the case when the + // debugger must suspend the thread. Example: if the given thread is in the middle of + // throwing a managed exception when the debugger breaks in, then TS_SyncSuspended + // will be set due to the debugger's breaking, but USER_SUSPENDED will not be set, so + // we'll think the UM chain should be tracked, resulting in a stack that differs from + // Whidbey. + if (m_pProcess->IsThreadSuspendedOrHijacked(m_pThread)) + return FALSE; + + return TRUE; +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::Populate +// +// Description: +// Walk the entire stack and populate the arrays of stack frames and stack chains. +// + +void ShimStackWalk::Populate() +{ + HRESULT hr = S_OK; + + // query for the ICDThread3 interface + RSExtSmartPtr<ICorDebugThread3> pThread3; + hr = m_pThread->QueryInterface(IID_ICorDebugThread3, reinterpret_cast<void **>(&pThread3)); + IfFailThrow(hr); + + // create the ICDStackWalk + RSExtSmartPtr<ICorDebugStackWalk> pSW; + hr = pThread3->CreateStackWalk(&pSW); + IfFailThrow(hr); + + // structs used to store information during the stackwalk + ChainInfo chainInfo; + StackWalkInfo swInfo; + + // use the ICDStackWalk to retrieve the internal frames + hr = pThread3->GetActiveInternalFrames(0, &(swInfo.m_cInternalFrames), NULL); + IfFailThrow(hr); + + // allocate memory for the internal frames + if (swInfo.m_cInternalFrames > 0) + { + // allocate memory for the array of RSExtSmartPtrs + swInfo.m_ppInternalFrame2.AllocOrThrow(swInfo.m_cInternalFrames); + + // create a temporary buffer of raw ICDInternalFrame2 to pass to the ICD API + NewArrayHolder<ICorDebugInternalFrame2 *> pTmpArray(new ICorDebugInternalFrame2* [swInfo.m_cInternalFrames]); + hr = pThread3->GetActiveInternalFrames(swInfo.m_cInternalFrames, + &(swInfo.m_cInternalFrames), + pTmpArray); + IfFailThrow(hr); + + // transfer the raw array to the RSExtSmartPtr array + for (UINT32 i = 0; i < swInfo.m_cInternalFrames; i++) + { + // Assign() increments the ref count + swInfo.m_ppInternalFrame2.Assign(i, pTmpArray[i]); + pTmpArray[i]->Release(); + } + pTmpArray.Clear(); + } + + // + // This is basically how the loop works: + // 1) Determine whether we should process the next internal frame or the next stack frame. + // 2) If we are skipping frames, the only thing we need to do is to check whether we have reached + // the parent frame. + // 3) Process CHAIN_ENTER_MANAGED/CHAIN_ENTER_UNMANAGED chains + // 4) Append the frame to the cache. + // 5) Handle other types of chains. + // 6) Advance to the next frame. + // 7) Check if we should exit the loop. + // + while (true) + { + // reset variables used in the loop + swInfo.ResetForNextFrame(); + + // retrieve the next stack frame if it's available + RSExtSmartPtr<ICorDebugFrame> pFrame; + if (!swInfo.ExhaustedAllStackFrames()) + { + hr = pSW->GetFrame(&pFrame); + IfFailThrow(hr); + } + + // This next clause processes the current frame, regardless of whether it's an internal frame or a + // stack frame. Normally, "pFrame != NULL" is a good enough check, except for the case where we + // have exhausted all the stack frames but still have internal frames to process. + if ((pFrame != NULL) || swInfo.ExhaustedAllStackFrames()) + { + // prefetch the internal frame type + if (!swInfo.ExhaustedAllInternalFrames()) + { + swInfo.m_internalFrameType = GetInternalFrameType(swInfo.GetCurrentInternalFrame()); + } + + // We cannot have exhausted both the stack frames and the internal frames when we get to here. + // We should have exited the loop if we have exhausted both types of frames. + if (swInfo.ExhaustedAllStackFrames()) + { + swInfo.m_fProcessingInternalFrame = true; + } + else if (swInfo.ExhaustedAllInternalFrames()) + { + swInfo.m_fProcessingInternalFrame = false; + } + else + { + // check whether we should process the next internal frame or the next stack frame + swInfo.m_fProcessingInternalFrame = (CheckInternalFrame(pFrame, &swInfo, pThread3, pSW) == TRUE); + } + + // The only thing we do while we are skipping frames is to check whether we have reached the + // parent frame, and we only need to check if we are processing a stack frame. + if (swInfo.IsSkippingFrame()) + { + if (!swInfo.m_fProcessingInternalFrame) + { + // Check whether we have reached the parent frame yet. + RSExtSmartPtr<ICorDebugNativeFrame2> pNFrame2; + hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame2, reinterpret_cast<void **>(&pNFrame2)); + IfFailThrow(hr); + + BOOL fIsParent = FALSE; + hr = swInfo.m_pChildFrame->IsMatchingParentFrame(pNFrame2, &fIsParent); + IfFailThrow(hr); + + if (fIsParent) + { + swInfo.m_pChildFrame.Clear(); + } + } + } + else if(swInfo.m_fProcessingInternalFrame && !chainInfo.m_fLeafNativeContextIsValid && + swInfo.m_internalFrameType == STUBFRAME_M2U) + { + // Filter this frame out entirely + // This occurs because InlinedCallFrames get placed inside leaf managed methods. + // The frame gets erected before the native call is made and destroyed afterwards + // but there is a window in which the debugger could stop where the internal frame + // is live but we are executing jitted code. See Dev10 issue 743230 + // It is quite possible other frames have this same pattern if the debugger were + // stopped right at the spot where they are being constructed. And that is + // just a facet of the general data structure consistency problems the debugger + // will always face + } + else + { + // Don't add any frame just yet. We need to deal with any unmanaged chain + // we are tracking first. + + // track the current enter-unmanaged chain and/or enter-managed chain + TrackUMChain(&chainInfo, &swInfo); + + if (swInfo.m_fProcessingInternalFrame) + { + // Check if this is a leaf internal frame. If so, check its frame type. + // In V2, code:DebuggerWalkStackProc doesn't expose chains derived from leaf internal + // frames of type TYPE_INTERNAL. However, V2 still exposes leaf M2U and U2M internal + // frames. + if (swInfo.IsLeafFrame()) + { + if (swInfo.m_internalFrameType == STUBFRAME_EXCEPTION) + { + // We need to make sure we don't accidentally send an enter-unmanaged chain + // because of the leaf STUBFRAME_EXCEPTION. + chainInfo.CancelUMChain(); + swInfo.m_fSkipChain = true; + } + } + + _ASSERTE(!swInfo.IsSkippingFrame()); + if (ConvertInternalFrameToDynamicMethod(&swInfo)) + { + // We have just converted a STUBFRAME_JIT_COMPILATION to a + // STUBFRAME_LIGHTWEIGHT_FUNCTION (or to NULL). Since the latter frame type doesn't + // map to any chain in V2, let's skip the chain handling. + swInfo.m_fSkipChain = true; + + // We may have converted to NULL, which means that we are dealing with an IL stub + // and we shouldn't expose it. + if (swInfo.GetCurrentInternalFrame() != NULL) + { + AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo); + } + } + else + { + // One more check before we append the internal frame: make sure the frame type is a + // V2 frame type first. + if (!IsV3FrameType(swInfo.m_internalFrameType)) + { + AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo); + } + } + } + else + { + if (!chainInfo.m_fNeedEnterManagedChain) + { + // If we have hit any managed stack frame, then we may need to send + // an enter-managed chain later. Save the CONTEXT now. + SaveChainContext(pSW, &chainInfo, &(chainInfo.m_leafManagedContext)); + chainInfo.m_fNeedEnterManagedChain = true; + } + + // We are processing a stack frame. + // Only append the frame if it's NOT a dynamic method. + _ASSERTE(!swInfo.IsSkippingFrame()); + if (ConvertStackFrameToDynamicMethod(pFrame, &swInfo)) + { + // We have converted a ICDNativeFrame for an IL method without metadata to an + // ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION (or to NULL). + // Fortunately, we don't have to update any state here + // (e.g. m_fProcessingInternalFrame) because the rest of the loop doesn't care. + if (swInfo.GetCurrentInternalFrame() != NULL) + { + AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo); + } + } + else + { + AppendFrame(pFrame, &swInfo); + } + + // If we have just processed a child frame, we should start skipping. + // Get the ICDNativeFrame2 pointer to check. + RSExtSmartPtr<ICorDebugNativeFrame2> pNFrame2; + hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame2, reinterpret_cast<void **>(&pNFrame2)); + IfFailThrow(hr); + + if (pNFrame2 != NULL) + { + BOOL fIsChild = FALSE; + hr = pNFrame2->IsChild(&fIsChild); + IfFailThrow(hr); + + if (fIsChild) + { + swInfo.m_pChildFrame.Assign(pNFrame2); + } + } + } + } + } // process the current frame (managed stack frame or internal frame) + + // We can take care of other types of chains here, but only do so if we are not currently skipping + // child frames. + if (!swInfo.IsSkippingFrame()) + { + if ((pFrame == NULL) && + !swInfo.ExhaustedAllStackFrames()) + { + // We are here because we are processing a native marker stack frame, not because + // we have exhausted all the stack frames. + + // We need to save the CONTEXT to start tracking an unmanaged chain. + SaveChainContext(pSW, &chainInfo, &(chainInfo.m_leafNativeContext)); + chainInfo.m_fLeafNativeContextIsValid = true; + + // begin tracking UM chain if we're supposed to + if (ShouldTrackUMChain(&swInfo)) + { + chainInfo.m_reason = CHAIN_ENTER_UNMANAGED; + } + } + else + { + // handle other types of chains + if (swInfo.m_fProcessingInternalFrame) + { + if (!swInfo.m_fSkipChain) + { + BOOL fNewChain = FALSE; + + switch (swInfo.m_internalFrameType) + { + case STUBFRAME_M2U: // fall through + case STUBFRAME_U2M: // fall through + // These frame types are tracked specially. + break; + + case STUBFRAME_APPDOMAIN_TRANSITION: // fall through + case STUBFRAME_LIGHTWEIGHT_FUNCTION: // fall through + case STUBFRAME_INTERNALCALL: + // These frame types don't correspond to chains. + break; + + case STUBFRAME_FUNC_EVAL: + chainInfo.m_reason = CHAIN_FUNC_EVAL; + fNewChain = TRUE; + break; + + case STUBFRAME_CLASS_INIT: // fall through + case STUBFRAME_JIT_COMPILATION: + // In Whidbey, these two frame types are the same. + chainInfo.m_reason = CHAIN_CLASS_INIT; + fNewChain = TRUE; + break; + + case STUBFRAME_EXCEPTION: + chainInfo.m_reason = CHAIN_EXCEPTION_FILTER; + fNewChain = TRUE; + break; + + case STUBFRAME_SECURITY: + chainInfo.m_reason = CHAIN_SECURITY; + fNewChain = TRUE; + break; + + default: + // We can only reach this case if we have converted an IL stub to NULL. + _ASSERTE(swInfo.HasConvertedFrame()); + break; + } + + if (fNewChain) + { + chainInfo.m_rootFP = GetFramePointerForChain(swInfo.GetCurrentInternalFrame()); + AppendChain(&chainInfo, &swInfo); + } + } + } // chain handling for an internl frame + } // chain handling for a managed stack frame or an internal frame + } // chain handling + + // Reset the flag for leaf frame if we have processed any frame. The only case where we should + // not reset this flag is if the ICDStackWalk is stopped at a native stack frame on creation. + if (swInfo.IsLeafFrame()) + { + if (swInfo.m_fProcessingInternalFrame || (pFrame != NULL)) + { + swInfo.m_fLeafFrame = false; + } + } + + // advance to the next frame + if (swInfo.m_fProcessingInternalFrame) + { + swInfo.m_curInternalFrame += 1; + } + else + { + hr = pSW->Next(); + IfFailThrow(hr); + + // check for the end of stack condition + if (hr == CORDBG_S_AT_END_OF_STACK) + { + // By the time we finish the stackwalk, all child frames should have been matched with their + // respective parent frames. + _ASSERTE(!swInfo.IsSkippingFrame()); + + swInfo.m_fExhaustedAllStackFrames = true; + } + } + + // Break out of the loop if we have exhausted all the frames. + if (swInfo.ExhaustedAllFrames()) + { + break; + } + } + + // top off the stackwalk with a thread start chain + chainInfo.m_reason = CHAIN_THREAD_START; + chainInfo.m_rootFP = ROOT_MOST_FRAME; // In Whidbey, we actually use the cached stack base value. + AppendChain(&chainInfo, &swInfo); +} + +// the caller is responsible for addref and release +ICorDebugThread * ShimStackWalk::GetThread() +{ + return m_pThread; +} + +// the caller is responsible for addref and release +ShimChain * ShimStackWalk::GetChain(UINT32 index) +{ + if (index >= (UINT32)(m_stackChains.Count())) + { + return NULL; + } + else + { + return *(m_stackChains.Get((int)index)); + } +} + +// the caller is responsible for addref and release +ICorDebugFrame * ShimStackWalk::GetFrame(UINT32 index) +{ + if (index >= (UINT32)(m_stackFrames.Count())) + { + return NULL; + } + else + { + return *(m_stackFrames.Get((int)index)); + } +} + +ULONG ShimStackWalk::GetChainCount() +{ + return m_stackChains.Count(); +} + +ULONG ShimStackWalk::GetFrameCount() +{ + return m_stackFrames.Count(); +} + +RSLock * ShimStackWalk::GetShimLock() +{ + return m_pProcess->GetShimLock(); +} + + +// ---------------------------------------------------------------------------- +// ShimStackWalk::AddChainEnum +// +// Description: +// Add the specified ShimChainEnum to the head of the linked list of ShimChainEnums on the ShimStackWalk. +// +// Arguments: +// * pChainEnum - the ShimChainEnum to be added +// + +void ShimStackWalk::AddChainEnum(ShimChainEnum * pChainEnum) +{ + pChainEnum->SetNext(m_pChainEnumList); + if (m_pChainEnumList != NULL) + { + m_pChainEnumList->Release(); + } + + m_pChainEnumList = pChainEnum; + if (m_pChainEnumList != NULL) + { + m_pChainEnumList->AddRef(); + } +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::AddFrameEnum +// +// Description: +// Add the specified ShimFrameEnum to the head of the linked list of ShimFrameEnums on the ShimStackWalk. +// +// Arguments: +// * pFrameEnum - the ShimFrameEnum to be added +// + +void ShimStackWalk::AddFrameEnum(ShimFrameEnum * pFrameEnum) +{ + pFrameEnum->SetNext(m_pFrameEnumList); + if (m_pFrameEnumList != NULL) + { + m_pFrameEnumList->Release(); + } + + m_pFrameEnumList = pFrameEnum; + if (m_pFrameEnumList != NULL) + { + m_pFrameEnumList->AddRef(); + } +} + +// Return the ICDThread associated with the current ShimStackWalk as a key for ShimStackWalkHashTableTraits. +ICorDebugThread * ShimStackWalk::GetKey() +{ + return m_pThread; +} + +// Hash a given ICDThread, which is used as the key for ShimStackWalkHashTableTraits. +//static +UINT32 ShimStackWalk::Hash(ICorDebugThread * pThread) +{ + // just return the pointer value + return (UINT32)(size_t)pThread; +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::IsLeafFrame +// +// Description: +// Check whether the specified frame is the leaf frame. +// +// Arguments: +// * pFrame - frame to be checked +// +// Return Value: +// Return TRUE if the specified frame is the leaf frame. +// Return FALSE otherwise. +// +// Notes: +// * The definition of the leaf frame in V2 is the frame at the leaf of the leaf chain. +// + +BOOL ShimStackWalk::IsLeafFrame(ICorDebugFrame * pFrame) +{ + // check if we have any chain + if (GetChainCount() > 0) + { + // check if the leaf chain has any frame + if (GetChain(0)->GetLastFrameIndex() > 0) + { + return IsSameFrame(pFrame, GetFrame(0)); + } + } + return FALSE; +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::IsSameFrame +// +// Description: +// Given two ICDFrames, check if they refer to the same frame. +// This is much more than a pointer comparison. This function actually checks the frame address, +// the stack pointer, etc. to make sure if the frames are the same. +// +// Arguments: +// * pLeft - frame to be compared +// * pRight - frame to be compared +// +// Return Value: +// Return TRUE if the two ICDFrames represent the same frame. +// + +BOOL ShimStackWalk::IsSameFrame(ICorDebugFrame * pLeft, ICorDebugFrame * pRight) +{ + HRESULT hr = E_FAIL; + + // Quick check #1: If the pointers are the same then the two frames are the same (duh!). + if (pLeft == pRight) + { + return TRUE; + } + + RSExtSmartPtr<ICorDebugNativeFrame> pLeftNativeFrame; + hr = pLeft->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pLeftNativeFrame)); + + if (SUCCEEDED(hr)) + { + // The left frame is a stack frame. + RSExtSmartPtr<ICorDebugNativeFrame> pRightNativeFrame; + hr = pRight->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pRightNativeFrame)); + + if (FAILED(hr)) + { + // The right frame is NOT a stack frame. + return FALSE; + } + else + { + // Quick check #2: If the IPs are different then the two frames are not the same (duh!). + ULONG32 leftOffset; + ULONG32 rightOffset; + + hr = pLeftNativeFrame->GetIP(&leftOffset); + IfFailThrow(hr); + + hr = pRightNativeFrame->GetIP(&rightOffset); + IfFailThrow(hr); + + if (leftOffset != rightOffset) + { + return FALSE; + } + + // real check + CORDB_ADDRESS leftStart; + CORDB_ADDRESS leftEnd; + CORDB_ADDRESS rightStart; + CORDB_ADDRESS rightEnd; + + hr = pLeftNativeFrame->GetStackRange(&leftStart, &leftEnd); + IfFailThrow(hr); + + hr = pRightNativeFrame->GetStackRange(&rightStart, &rightEnd); + IfFailThrow(hr); + + return ((leftStart == rightStart) && (leftEnd == rightEnd)); + } + } + else + { + RSExtSmartPtr<ICorDebugInternalFrame2> pLeftInternalFrame2; + hr = pLeft->QueryInterface(IID_ICorDebugInternalFrame2, + reinterpret_cast<void **>(&pLeftInternalFrame2)); + + if (SUCCEEDED(hr)) + { + // The left frame is an internal frame. + RSExtSmartPtr<ICorDebugInternalFrame2> pRightInternalFrame2; + hr = pRight->QueryInterface(IID_ICorDebugInternalFrame2, + reinterpret_cast<void **>(&pRightInternalFrame2)); + + if (FAILED(hr)) + { + return FALSE; + } + else + { + // The right frame is also an internal frame. + + // Check the frame address. + CORDB_ADDRESS leftFrameAddr; + CORDB_ADDRESS rightFrameAddr; + + hr = pLeftInternalFrame2->GetAddress(&leftFrameAddr); + IfFailThrow(hr); + + hr = pRightInternalFrame2->GetAddress(&rightFrameAddr); + IfFailThrow(hr); + + return (leftFrameAddr == rightFrameAddr); + } + } + + return FALSE; + } +} + +// This is the shim implementation of ICDThread::EnumerateChains(). +void ShimStackWalk::EnumerateChains(ICorDebugChainEnum ** ppChainEnum) +{ + NewHolder<ShimChainEnum> pChainEnum(new ShimChainEnum(this, GetShimLock())); + + *ppChainEnum = pChainEnum; + (*ppChainEnum)->AddRef(); + AddChainEnum(pChainEnum); + + pChainEnum.SuppressRelease(); +} + +// This is the shim implementation of ICDThread::GetActiveChain(). +void ShimStackWalk::GetActiveChain(ICorDebugChain ** ppChain) +{ + if (GetChainCount() == 0) + { + *ppChain = NULL; + } + else + { + *ppChain = static_cast<ICorDebugChain *>(GetChain(0)); + (*ppChain)->AddRef(); + } +} + +// This is the shim implementation of ICDThread::GetActiveFrame(). +void ShimStackWalk::GetActiveFrame(ICorDebugFrame ** ppFrame) +{ + // + // Make sure two things: + // 1) We have at least one frame. + // 2) The leaf frame is in the leaf chain, i.e. the leaf chain is not empty. + // + if ((GetFrameCount() == 0) || + (GetChain(0)->GetLastFrameIndex() == 0)) + { + *ppFrame = NULL; + } + else + { + *ppFrame = GetFrame(0); + (*ppFrame)->AddRef(); + } +} + +// This is the shim implementation of ICDThread::GetRegisterSet(). +void ShimStackWalk::GetActiveRegisterSet(ICorDebugRegisterSet ** ppRegisterSet) +{ + _ASSERTE(GetChainCount() != 0); + _ASSERTE(GetChain(0) != NULL); + + // Return the register set of the leaf chain. + HRESULT hr = GetChain(0)->GetRegisterSet(ppRegisterSet); + IfFailThrow(hr); +} + +// This is the shim implementation of ICDFrame::GetChain(). +void ShimStackWalk::GetChainForFrame(ICorDebugFrame * pFrame, ICorDebugChain ** ppChain) +{ + CORDB_ADDRESS frameStart; + CORDB_ADDRESS frameEnd; + IfFailThrow(pFrame->GetStackRange(&frameStart, &frameEnd)); + + for (UINT32 i = 0; i < GetChainCount(); i++) + { + ShimChain * pCurChain = GetChain(i); + + CORDB_ADDRESS chainStart; + CORDB_ADDRESS chainEnd; + IfFailThrow(pCurChain->GetStackRange(&chainStart, &chainEnd)); + + if ((chainStart <= frameStart) && (frameEnd <= chainEnd)) + { + // We need to check the next chain as well since some chains overlap at the boundary. + // If the current chain is the last one, no additional checking is required. + if (i < (GetChainCount() - 1)) + { + ShimChain * pNextChain = GetChain(i + 1); + + CORDB_ADDRESS nextChainStart; + CORDB_ADDRESS nextChainEnd; + IfFailThrow(pNextChain->GetStackRange(&nextChainStart, &nextChainEnd)); + + if ((nextChainStart <= frameStart) && (frameEnd <= nextChainEnd)) + { + // The frame lies in the stack ranges of two chains. This can only happn at the boundary. + if (pCurChain->GetFirstFrameIndex() == pCurChain->GetLastFrameIndex()) + { + // Make sure the next chain is not empty. + _ASSERTE(pNextChain->GetFirstFrameIndex() != pNextChain->GetLastFrameIndex()); + + // The current chain is empty, so the chain we want is the next one. + pCurChain = pNextChain; + } + // If the next chain is empty, then we'll just return the current chain and no additional + // work is needed. If the next chain is not empty, then we have more checking to do. + else if (pNextChain->GetFirstFrameIndex() != pNextChain->GetLastFrameIndex()) + { + // Both chains are non-empty. + if (IsSameFrame(GetFrame(pNextChain->GetFirstFrameIndex()), pFrame)) + { + // The same frame cannot be in both chains. + _ASSERTE(!IsSameFrame(GetFrame(pCurChain->GetLastFrameIndex() - 1), pFrame)); + pCurChain = pNextChain; + } + else + { + _ASSERTE(IsSameFrame(GetFrame(pCurChain->GetLastFrameIndex() - 1), pFrame)); + } + } + } + } + + *ppChain = static_cast<ICorDebugChain *>(pCurChain); + (*ppChain)->AddRef(); + return; + } + } +} + +// This is the shim implementation of ICDFrame::GetCaller(). +void ShimStackWalk::GetCallerForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCallerFrame) +{ + for (UINT32 i = 0; i < GetChainCount(); i++) + { + ShimChain * pCurChain = GetChain(i); + + for (UINT32 j = pCurChain->GetFirstFrameIndex(); j < pCurChain->GetLastFrameIndex(); j++) + { + if (IsSameFrame(GetFrame(j), pFrame)) + { + // Check whether this is the last frame in the chain. + UINT32 callerFrameIndex = j + 1; + if (callerFrameIndex < pCurChain->GetLastFrameIndex()) + { + *ppCallerFrame = static_cast<ICorDebugFrame *>(GetFrame(callerFrameIndex)); + (*ppCallerFrame)->AddRef(); + } + else + { + *ppCallerFrame = NULL; + } + return; + } + } + } +} + +// This is the shim implementation of ICDFrame::GetCallee(). +void ShimStackWalk::GetCalleeForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCalleeFrame) +{ + for (UINT32 i = 0; i < GetChainCount(); i++) + { + ShimChain * pCurChain = GetChain(i); + + for (UINT32 j = pCurChain->GetFirstFrameIndex(); j < pCurChain->GetLastFrameIndex(); j++) + { + if (IsSameFrame(GetFrame(j), pFrame)) + { + // Check whether this is the first frame in the chain. + if (j > pCurChain->GetFirstFrameIndex()) + { + UINT32 calleeFrameIndex = j - 1; + *ppCalleeFrame = static_cast<ICorDebugFrame *>(GetFrame(calleeFrameIndex)); + (*ppCalleeFrame)->AddRef(); + } + else + { + *ppCalleeFrame = NULL; + } + return; + } + } + } +} + +FramePointer ShimStackWalk::GetFramePointerForChain(DT_CONTEXT * pContext) +{ + return FramePointer::MakeFramePointer(CORDbgGetSP(pContext)); +} + +FramePointer ShimStackWalk::GetFramePointerForChain(ICorDebugInternalFrame2 * pInternalFrame2) +{ + CORDB_ADDRESS frameAddr; + HRESULT hr = pInternalFrame2->GetAddress(&frameAddr); + IfFailThrow(hr); + + return FramePointer::MakeFramePointer(reinterpret_cast<void *>(frameAddr)); +} + +CorDebugInternalFrameType ShimStackWalk::GetInternalFrameType(ICorDebugInternalFrame2 * pFrame2) +{ + HRESULT hr = E_FAIL; + + // Retrieve the frame type of the internal frame. + RSExtSmartPtr<ICorDebugInternalFrame> pFrame; + hr = pFrame2->QueryInterface(IID_ICorDebugInternalFrame, reinterpret_cast<void **>(&pFrame)); + IfFailThrow(hr); + + CorDebugInternalFrameType type; + hr = pFrame->GetFrameType(&type); + IfFailThrow(hr); + + return type; +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::AppendFrame +// +// Description: +// Append the specified frame to the array and increment the counter. +// +// Arguments: +// * pFrame - the frame to be added +// * pStackWalkInfo - contains information of the stackwalk +// + +void ShimStackWalk::AppendFrame(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo) +{ + // grow the + ICorDebugFrame ** ppFrame = m_stackFrames.AppendThrowing(); + + // Be careful of the AddRef() below. Once we do the addref, we need to save the pointer and + // explicitly release it. + *ppFrame = pFrame; + (*ppFrame)->AddRef(); + + pStackWalkInfo->m_cFrame += 1; +} + +// ---------------------------------------------------------------------------- +// Refer to comment of the overloaded function. +// + +void ShimStackWalk::AppendFrame(ICorDebugInternalFrame2 * pInternalFrame2, StackWalkInfo * pStackWalkInfo) +{ + RSExtSmartPtr<ICorDebugFrame> pFrame; + HRESULT hr = pInternalFrame2->QueryInterface(IID_ICorDebugFrame, reinterpret_cast<void **>(&pFrame)); + IfFailThrow(hr); + + AppendFrame(pFrame, pStackWalkInfo); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::AppendChainWorker +// +// Description: +// Append the specified chain to the array. +// +// Arguments: +// * pStackWalkInfo - contains information regarding the stackwalk +// * pLeafContext - the leaf CONTEXT of the chain to be added +// * fpRoot - the root boundary of the chain to be added +// * chainReason - the chain reason of the chain to be added +// * fIsManagedChain - whether the chain to be added is managed +// + +void ShimStackWalk::AppendChainWorker(StackWalkInfo * pStackWalkInfo, + DT_CONTEXT * pLeafContext, + FramePointer fpRoot, + CorDebugChainReason chainReason, + BOOL fIsManagedChain) +{ + // first, create the chain + NewHolder<ShimChain> pChain(new ShimChain(this, + pLeafContext, + fpRoot, + pStackWalkInfo->m_cChain, + pStackWalkInfo->m_firstFrameInChain, + pStackWalkInfo->m_cFrame, + chainReason, + fIsManagedChain, + GetShimLock())); + + // Grow the array and add the newly created chain. + // Once we call AddRef() we own the ShimChain and need to release it. + ShimChain ** ppChain = m_stackChains.AppendThrowing(); + *ppChain = pChain; + (*ppChain)->AddRef(); + + // update the counters on the StackWalkInfo + pStackWalkInfo->m_cChain += 1; + pStackWalkInfo->m_firstFrameInChain = pStackWalkInfo->m_cFrame; + + // If all goes well, suppress the release so that the ShimChain won't go away. + pChain.SuppressRelease(); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::AppendChain +// +// Description: +// Append the chain to the array. This function is also smart enough to send an enter-managed chain +// if necessary. In other words, this function may append two chains at the same time. +// +// Arguments: +// * pChainInfo - information on the chain to be added +// * pStackWalkInfo - information regarding the current stackwalk +// + +void ShimStackWalk::AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo) +{ + // Check if the chain to be added is managed or not. + BOOL fManagedChain = FALSE; + if ((pChainInfo->m_reason == CHAIN_ENTER_MANAGED) || + (pChainInfo->m_reason == CHAIN_CLASS_INIT) || + (pChainInfo->m_reason == CHAIN_SECURITY) || + (pChainInfo->m_reason == CHAIN_FUNC_EVAL)) + { + fManagedChain = TRUE; + } + + DT_CONTEXT * pChainContext = NULL; + if (fManagedChain) + { + // The chain to be added is managed itself. So we don't need to send an enter-managed chain. + pChainInfo->m_fNeedEnterManagedChain = false; + pChainContext = &(pChainInfo->m_leafManagedContext); + } + else + { + // The chain to be added is unmanaged. Check if we need to send an enter-managed chain. + if (pChainInfo->m_fNeedEnterManagedChain) + { + // We need to send an extra enter-managed chain. + _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid); + BYTE * sp = reinterpret_cast<BYTE *>(CORDbgGetSP(&(pChainInfo->m_leafNativeContext))); +#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_) + // Dev11 324806: on ARM we use the caller's SP for a frame's ending delimiter so we cannot + // subtract 4 bytes from the chain's ending delimiter else the frame might never be in range. + // TODO: revisit overlapping ranges on ARM, it would be nice to make it consistent with the other architectures. + sp -= sizeof(LPVOID); +#endif + FramePointer fp = FramePointer::MakeFramePointer(sp); + + AppendChainWorker(pStackWalkInfo, + &(pChainInfo->m_leafManagedContext), + fp, + CHAIN_ENTER_MANAGED, + TRUE); + + pChainInfo->m_fNeedEnterManagedChain = false; + } + _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid); + pChainContext = &(pChainInfo->m_leafNativeContext); + } + + // Add the actual chain. + AppendChainWorker(pStackWalkInfo, + pChainContext, + pChainInfo->m_rootFP, + pChainInfo->m_reason, + fManagedChain); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::SaveChainContext +// +// Description: +// Save the current CONTEXT on the ICDStackWalk into the specified CONTEXT. Also update the root end +// of the chain on the ChainInfo. +// +// Arguments: +// * pSW - the ICDStackWalk for the current stackwalk +// * pChainInfo - the ChainInfo keeping track of the current chain +// * pContext - the destination CONTEXT +// + +void ShimStackWalk::SaveChainContext(ICorDebugStackWalk * pSW, ChainInfo * pChainInfo, DT_CONTEXT * pContext) +{ + HRESULT hr = pSW->GetContext(CONTEXT_FULL, + sizeof(*pContext), + NULL, + reinterpret_cast<BYTE *>(pContext)); + IfFailThrow(hr); + + pChainInfo->m_rootFP = GetFramePointerForChain(pContext); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::CheckInternalFrame +// +// Description: +// Check whether the next frame to be processed should be the next internal frame or the next stack frame. +// +// Arguments: +// * pNextStackFrame - the next stack frame +// * pStackWalkInfo - information regarding the current stackwalk; also contains the next internal frame +// * pThread3 - the thread we are walking +// * pSW - the current stackwalk +// +// Return Value: +// Return TRUE if we should process an internal frame next. +// + +BOOL ShimStackWalk::CheckInternalFrame(ICorDebugFrame * pNextStackFrame, + StackWalkInfo * pStackWalkInfo, + ICorDebugThread3 * pThread3, + ICorDebugStackWalk * pSW) +{ + _ASSERTE(pNextStackFrame != NULL); + _ASSERTE(!pStackWalkInfo->ExhaustedAllInternalFrames()); + + HRESULT hr = E_FAIL; + BOOL fIsInternalFrameFirst = FALSE; + + // Special handling for the case where a managed method contains a M2U internal frame. + // Normally only IL stubs contain M2U internal frames, but we may have inlined pinvoke calls in + // optimized code. In that case, we would have an InlinedCallFrame in a normal managed method on x86. + // On WIN64, we would have a normal NDirectMethodFrame* in a normal managed method. + if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U) + { + // create a temporary ICDStackWalk + RSExtSmartPtr<ICorDebugStackWalk> pTmpSW; + hr = pThread3->CreateStackWalk(&pTmpSW); + IfFailThrow(hr); + + // retrieve the current CONTEXT + DT_CONTEXT ctx; + ctx.ContextFlags = DT_CONTEXT_FULL; + hr = pSW->GetContext(ctx.ContextFlags, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx)); + IfFailThrow(hr); + + // set the CONTEXT on the temporary ICDStackWalk + hr = pTmpSW->SetContext(SET_CONTEXT_FLAG_ACTIVE_FRAME, sizeof(ctx), reinterpret_cast<BYTE *>(&ctx)); + IfFailThrow(hr); + + // unwind the temporary ICDStackWalk by one frame + hr = pTmpSW->Next(); + IfFailThrow(hr); + + // Unwinding from a managed stack frame will land us either in a managed stack frame or a native + // stack frame. In either case, we have a CONTEXT. + hr = pTmpSW->GetContext(ctx.ContextFlags, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx)); + IfFailThrow(hr); + + // Get the SP from the CONTEXT. This is the caller SP. + CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(&ctx)); + + // get the frame address + CORDB_ADDRESS frameAddr = 0; + hr = pStackWalkInfo->GetCurrentInternalFrame()->GetAddress(&frameAddr); + IfFailThrow(hr); + + // Compare the frame address with the caller SP of the stack frame for the IL method without metadata. + fIsInternalFrameFirst = (frameAddr < sp); + } + else + { + hr = pStackWalkInfo->GetCurrentInternalFrame()->IsCloserToLeaf(pNextStackFrame, &fIsInternalFrameFirst); + IfFailThrow(hr); + } + + return fIsInternalFrameFirst; +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::ConvertInternalFrameToDynamicMethod +// +// Description: +// In V2, PrestubMethodFrames (PMFs) are exposed as one of two things: a chain of type +// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if +// the method being jitted is a dynamic method. On the other hand, in Arrowhead, we consistently expose +// PMFs as STUBFRAME_JIT_COMPILATION. This function determines if a STUBFRAME_JIT_COMPILATION should +// be exposed, and, if so, how to expose it. In the case where conversion is necessary, this function +// also updates the stackwalk information with the converted frame. +// +// Here are the rules for conversion: +// 1) If the method being jitted is an IL stub, we set the converted frame to NULL, and we return TRUE. +// 2) If the method being jitted is an LCG method, we set the converted frame to a +// STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return NULL. +// 3) Otherwise, we return FALSE. +// +// Arguments: +// * pStackWalkInfo - information about the current stackwalk +// +// Return Value: +// Return TRUE if a conversion has taken place. +// + +BOOL ShimStackWalk::ConvertInternalFrameToDynamicMethod(StackWalkInfo * pStackWalkInfo) +{ + HRESULT hr = E_FAIL; + + // QI for ICDFrame + RSExtSmartPtr<ICorDebugFrame> pOriginalFrame; + hr = pStackWalkInfo->GetCurrentInternalFrame()->QueryInterface( + IID_ICorDebugFrame, + reinterpret_cast<void **>(&pOriginalFrame)); + IfFailThrow(hr); + + // Ask the RS to do the real work. + CordbThread * pThread = static_cast<CordbThread *>(m_pThread.GetValue()); + pStackWalkInfo->m_fHasConvertedFrame = (TRUE == pThread->ConvertFrameForILMethodWithoutMetadata( + pOriginalFrame, + &(pStackWalkInfo->m_pConvertedInternalFrame2))); + + if (pStackWalkInfo->HasConvertedFrame()) + { + // We have a conversion. + if (pStackWalkInfo->GetCurrentInternalFrame() != NULL) + { + // We have a converted internal frame, so let's update the internal frame type. + RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame; + hr = pStackWalkInfo->GetCurrentInternalFrame()->QueryInterface( + IID_ICorDebugInternalFrame, + reinterpret_cast<void **>(&pInternalFrame)); + IfFailThrow(hr); + + hr = pInternalFrame->GetFrameType(&(pStackWalkInfo->m_internalFrameType)); + IfFailThrow(hr); + } + else + { + // The method being jitted is an IL stub, so let's not expose it. + pStackWalkInfo->m_internalFrameType = STUBFRAME_NONE; + } + } + + return pStackWalkInfo->HasConvertedFrame(); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::ConvertInternalFrameToDynamicMethod +// +// Description: +// In V2, LCG methods are exposed as internal frames of type STUBFRAME_LIGHTWEIGHT_FUNCTION. However, +// in Arrowhead, LCG methods are exposed as first-class stack frames, not internal frames. Thus, +// the shim needs to convert an ICDNativeFrame for a dynamic method in Arrowhead to an +// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs are not exposed +// in V2 at all. +// +// Here are the rules for conversion: +// 1) If the stack frame is for an IL stub, we set the converted frame to NULL, and we return TRUE. +// 2) If the stack frame is for an LCG method, we set the converted frame to a +// STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return NULL. +// 3) Otherwise, we return FALSE. +// +// Arguments: +// * pFrame - the frame to be checked and converted if necessary +// * pStackWalkInfo - information about the current stackwalk +// +// Return Value: +// Return TRUE if a conversion has taken place. +// + +BOOL ShimStackWalk::ConvertStackFrameToDynamicMethod(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo) +{ + // If this is not a dynamic method (i.e. LCG method or IL stub), then we don't need to do a conversion. + if (!IsILFrameWithoutMetadata(pFrame)) + { + return FALSE; + } + + // Ask the RS to do the real work. + CordbThread * pThread = static_cast<CordbThread *>(m_pThread.GetValue()); + pStackWalkInfo->m_fHasConvertedFrame = (TRUE == pThread->ConvertFrameForILMethodWithoutMetadata( + pFrame, + &(pStackWalkInfo->m_pConvertedInternalFrame2))); + + return pStackWalkInfo->HasConvertedFrame(); +} + +// ---------------------------------------------------------------------------- +// ShimStackWalk::TrackUMChain +// +// Description: +// Keep track of enter-unmanaged chains. Extend or cancel the chain as necesasry. +// +// Arguments: +// * pChainInfo - information on the current chain we are tracking +// * pStackWalkInfo - information regarding the current stackwalk +// +// Notes: +// * This logic is based on code:TrackUMChain on the LS. +// + +void ShimStackWalk::TrackUMChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo) +{ + if (!pChainInfo->IsTrackingUMChain()) + { + if (pStackWalkInfo->m_fProcessingInternalFrame) + { + if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U) + { + // If we hit an M2U frame out in the wild, convert it to an enter-unmanaged chain. + + // We can't hit an M2U frame without hitting a native stack frame + // first (we filter those). We should have already saved the CONTEXT. + // So just update the chain reason. + pChainInfo->m_reason = CHAIN_ENTER_UNMANAGED; + } + } + } + + BOOL fCreateUMChain = FALSE; + if (pChainInfo->IsTrackingUMChain()) + { + if (pStackWalkInfo->m_fProcessingInternalFrame) + { + // Extend the root end of the unmanaged chain. + pChainInfo->m_rootFP = GetFramePointerForChain(pStackWalkInfo->GetCurrentInternalFrame()); + + // 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 ((pStackWalkInfo->m_internalFrameType == STUBFRAME_APPDOMAIN_TRANSITION) || + (pStackWalkInfo->m_internalFrameType == STUBFRAME_FUNC_EVAL)) + { + pChainInfo->CancelUMChain(); + } + else if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U) + { + // If we hit an M2U frame, then go ahead and dispatch the UM chain now. + // This will likely also be an exit frame. + fCreateUMChain = TRUE; + } + else if ((pStackWalkInfo->m_internalFrameType == STUBFRAME_CLASS_INIT) || + (pStackWalkInfo->m_internalFrameType == STUBFRAME_EXCEPTION) || + (pStackWalkInfo->m_internalFrameType == STUBFRAME_SECURITY) || + (pStackWalkInfo->m_internalFrameType == STUBFRAME_JIT_COMPILATION)) + { + fCreateUMChain = TRUE; + } + } + else + { + // If we hit a managed stack frame when we are processing an unmanaged chain, then + // the chain is done. + fCreateUMChain = TRUE; + } + } + + if (fCreateUMChain) + { + // check whether we get any stack range + _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid); + FramePointer fpLeaf = GetFramePointerForChain(&(pChainInfo->m_leafNativeContext)); + + // Don't bother creating an unmanaged chain if the stack range is empty. + if (fpLeaf != pChainInfo->m_rootFP) + { + AppendChain(pChainInfo, pStackWalkInfo); + } + pChainInfo->CancelUMChain(); + } +} + +BOOL ShimStackWalk::IsV3FrameType(CorDebugInternalFrameType type) +{ + // These frame types are either new in Arrowhead or not used in V2. + if ((type == STUBFRAME_INTERNALCALL) || + (type == STUBFRAME_CLASS_INIT) || + (type == STUBFRAME_EXCEPTION) || + (type == STUBFRAME_SECURITY) || + (type == STUBFRAME_JIT_COMPILATION)) + { + return TRUE; + } + else + { + return FALSE; + } +} + +// Check whether a stack frame is for a dynamic method. The way to tell is if the stack frame has +// an ICDNativeFrame but no ICDILFrame. +BOOL ShimStackWalk::IsILFrameWithoutMetadata(ICorDebugFrame * pFrame) +{ + HRESULT hr = E_FAIL; + + RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame; + hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pNativeFrame)); + IfFailThrow(hr); + + if (pNativeFrame != NULL) + { + RSExtSmartPtr<ICorDebugILFrame> pILFrame; + hr = pFrame->QueryInterface(IID_ICorDebugILFrame, reinterpret_cast<void **>(&pILFrame)); + + if (FAILED(hr) || (pILFrame == NULL)) + { + return TRUE; + } + } + + return FALSE; +} + +ShimStackWalk::StackWalkInfo::StackWalkInfo() + : m_cChain(0), + m_cFrame(0), + m_firstFrameInChain(0), + m_cInternalFrames(0), + m_curInternalFrame(0), + m_internalFrameType(STUBFRAME_NONE), + m_fExhaustedAllStackFrames(false), + m_fProcessingInternalFrame(false), + m_fSkipChain(false), + m_fLeafFrame(true), + m_fHasConvertedFrame(false) +{ + m_pChildFrame.Assign(NULL); + m_pConvertedInternalFrame2.Assign(NULL); +} + +ShimStackWalk::StackWalkInfo::~StackWalkInfo() +{ + if (m_pChildFrame != NULL) + { + m_pChildFrame.Clear(); + } + + if (m_pConvertedInternalFrame2 != NULL) + { + m_pConvertedInternalFrame2.Clear(); + } + + if (!m_ppInternalFrame2.IsEmpty()) + { + m_ppInternalFrame2.Clear(); + } +} + +void ShimStackWalk::StackWalkInfo::ResetForNextFrame() +{ + m_pConvertedInternalFrame2.Clear(); + m_internalFrameType = STUBFRAME_NONE; + m_fProcessingInternalFrame = false; + m_fSkipChain = false; + m_fHasConvertedFrame = false; +} + +// Check whether we have exhausted both internal frames and stack frames. +bool ShimStackWalk::StackWalkInfo::ExhaustedAllFrames() +{ + return (ExhaustedAllStackFrames() && ExhaustedAllInternalFrames()); +} + +bool ShimStackWalk::StackWalkInfo::ExhaustedAllStackFrames() +{ + return m_fExhaustedAllStackFrames; +} + +bool ShimStackWalk::StackWalkInfo::ExhaustedAllInternalFrames() +{ + return (m_curInternalFrame == m_cInternalFrames); +} + +ICorDebugInternalFrame2 * ShimStackWalk::StackWalkInfo::GetCurrentInternalFrame() +{ + _ASSERTE(!ExhaustedAllInternalFrames() || HasConvertedFrame()); + + if (HasConvertedFrame()) + { + return m_pConvertedInternalFrame2; + } + else + { + return m_ppInternalFrame2[m_curInternalFrame]; + } +} + +BOOL ShimStackWalk::StackWalkInfo::IsLeafFrame() +{ + return m_fLeafFrame; +} + +BOOL ShimStackWalk::StackWalkInfo::IsSkippingFrame() +{ + return (m_pChildFrame != NULL); +} + +BOOL ShimStackWalk::StackWalkInfo::HasConvertedFrame() +{ + return m_fHasConvertedFrame; +} + + +ShimChain::ShimChain(ShimStackWalk * pSW, + DT_CONTEXT * pContext, + FramePointer fpRoot, + UINT32 chainIndex, + UINT32 frameStartIndex, + UINT32 frameEndIndex, + CorDebugChainReason chainReason, + BOOL fIsManaged, + RSLock * pShimLock) + : m_context(*pContext), + m_fpRoot(fpRoot), + m_pStackWalk(pSW), + m_refCount(0), + m_chainIndex(chainIndex), + m_frameStartIndex(frameStartIndex), + m_frameEndIndex(frameEndIndex), + m_chainReason(chainReason), + m_fIsManaged(fIsManaged), + m_fIsNeutered(FALSE), + m_pShimLock(pShimLock) +{ +} + +ShimChain::~ShimChain() +{ + _ASSERTE(IsNeutered()); +} + +void ShimChain::Neuter() +{ + m_fIsNeutered = TRUE; +} + +BOOL ShimChain::IsNeutered() +{ + return m_fIsNeutered; +} + +ULONG STDMETHODCALLTYPE ShimChain::AddRef() +{ + return InterlockedIncrement((LONG *)&m_refCount); +} + +ULONG STDMETHODCALLTYPE ShimChain::Release() +{ + LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount); + _ASSERTE(newRefCount >= 0); + + if (newRefCount == 0) + { + delete this; + } + return newRefCount; +} + +HRESULT ShimChain::QueryInterface(REFIID id, void ** pInterface) +{ + if (id == IID_ICorDebugChain) + { + *pInterface = static_cast<ICorDebugChain *>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugChain *>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +// Returns the thread to which this chain belongs. +HRESULT ShimChain::GetThread(ICorDebugThread ** ppThread) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **); + + *ppThread = m_pStackWalk->GetThread(); + (*ppThread)->AddRef(); + + return S_OK; +} + +// Get the range on the stack that this chain matches against. +// pStart is the leafmost; pEnd is the rootmost. +// This is particularly used in interop-debugging to get native stack traces +// for the UM portions of the stack +HRESULT ShimChain::GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd) +{ + HRESULT hr = S_OK; + EX_TRY + { + THROW_IF_NEUTERED(this); + + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pStart, CORDB_ADDRESS *); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pEnd, CORDB_ADDRESS *); + + // Return the leafmost end of the stack range. + // The leafmost end is represented by the register set. + if (pStart) + { + *pStart = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(&m_context)); + } + + // Return the rootmost end of the stack range. It is represented by the frame pointer of the chain. + if (pEnd) + { + *pEnd = PTR_TO_CORDB_ADDRESS(m_fpRoot.GetSPValue()); + } + } + EX_CATCH_HRESULT(hr); + return hr; +} + +HRESULT ShimChain::GetContext(ICorDebugContext ** ppContext) +{ + return E_NOTIMPL; +} + +// Return the next chain which is closer to the root. +// Currently this is just a wrapper over GetNext(). +HRESULT ShimChain::GetCaller(ICorDebugChain ** ppChain) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **); + + return GetNext(ppChain); +} + +// Return the previous chain which is closer to the leaf. +// Currently this is just a wrapper over GetPrevious(). +HRESULT ShimChain::GetCallee(ICorDebugChain ** ppChain) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **); + + return GetPrevious(ppChain); +} + +// Return the previous chain which is closer to the leaf. +HRESULT ShimChain::GetPrevious(ICorDebugChain ** ppChain) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **); + + *ppChain = NULL; + if (m_chainIndex != 0) + { + *ppChain = m_pStackWalk->GetChain(m_chainIndex - 1); + } + + if (*ppChain != NULL) + { + (*ppChain)->AddRef(); + } + + return S_OK; +} + +// Return the next chain which is closer to the root. +HRESULT ShimChain::GetNext(ICorDebugChain ** ppChain) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **); + + *ppChain = m_pStackWalk->GetChain(m_chainIndex + 1); + if (*ppChain != NULL) + { + (*ppChain)->AddRef(); + } + + return S_OK; +} + +// Return whether the chain contains frames running managed code. +HRESULT ShimChain::IsManaged(BOOL * pManaged) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pManaged, BOOL *); + + *pManaged = m_fIsManaged; + + return S_OK; +} + +// Return an enumerator to iterate through the frames contained in this chain. +HRESULT ShimChain::EnumerateFrames(ICorDebugFrameEnum ** ppFrames) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppFrames, ICorDebugFrameEnum **); + + HRESULT hr = S_OK; + EX_TRY + { + ShimStackWalk * pSW = GetShimStackWalk(); + NewHolder<ShimFrameEnum> pFrameEnum(new ShimFrameEnum(pSW, this, m_frameStartIndex, m_frameEndIndex, m_pShimLock)); + + *ppFrames = pFrameEnum; + (*ppFrames)->AddRef(); + + // link the new ShimFramEnum into the list on the ShimStackWalk + pSW->AddFrameEnum(pFrameEnum); + + pFrameEnum.SuppressRelease(); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +// Return an enumerator to iterate through the frames contained in this chain. +// Note that this function will only succeed if the cached stack trace is valid. +HRESULT ShimChain::GetActiveFrame(ICorDebugFrame ** ppFrame) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **); + (*ppFrame) = NULL; + + HRESULT hr = S_OK; + + // Chains may be empty, so they have no active frame. + if (m_frameStartIndex == m_frameEndIndex) + { + *ppFrame = NULL; + } + else + { + *ppFrame = m_pStackWalk->GetFrame(m_frameStartIndex); + (*ppFrame)->AddRef(); + } + + return hr; +} + +// Return the register set of the leaf end of the chain +HRESULT ShimChain::GetRegisterSet(ICorDebugRegisterSet ** ppRegisters) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **); + + HRESULT hr = S_OK; + EX_TRY + { + CordbThread * pThread = static_cast<CordbThread *>(m_pStackWalk->GetThread()); + + // This is a private hook for calling back into the RS. Alternatively, we could have created a + // ShimRegisterSet, but that's too much work for now. + pThread->CreateCordbRegisterSet(&m_context, + (m_chainIndex == 0), + m_chainReason, + ppRegisters); + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// Return the chain reason +HRESULT ShimChain::GetReason(CorDebugChainReason * pReason) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pReason, CorDebugChainReason *); + + *pReason = m_chainReason; + + return S_OK; +} + +ShimStackWalk * ShimChain::GetShimStackWalk() +{ + return m_pStackWalk; +} + +UINT32 ShimChain::GetFirstFrameIndex() +{ + return this->m_frameStartIndex; +} + +UINT32 ShimChain::GetLastFrameIndex() +{ + return this->m_frameEndIndex; +} + + +ShimChainEnum::ShimChainEnum(ShimStackWalk * pSW, RSLock * pShimLock) + : m_pStackWalk(pSW), + m_pNext(NULL), + m_currentChainIndex(0), + m_refCount(0), + m_fIsNeutered(FALSE), + m_pShimLock(pShimLock) +{ +} + +ShimChainEnum::~ShimChainEnum() +{ + _ASSERTE(IsNeutered()); +} + +void ShimChainEnum::Neuter() +{ + if (IsNeutered()) + { + return; + } + + m_fIsNeutered = TRUE; +} + +BOOL ShimChainEnum::IsNeutered() +{ + return m_fIsNeutered; +} + + +ULONG STDMETHODCALLTYPE ShimChainEnum::AddRef() +{ + return InterlockedIncrement((LONG *)&m_refCount); +} + +ULONG STDMETHODCALLTYPE ShimChainEnum::Release() +{ + LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount); + _ASSERTE(newRefCount >= 0); + + if (newRefCount == 0) + { + delete this; + } + return newRefCount; +} + +HRESULT ShimChainEnum::QueryInterface(REFIID id, void ** ppInterface) +{ + if (id == IID_ICorDebugChainEnum) + { + *ppInterface = static_cast<ICorDebugChainEnum *>(this); + } + else if (id == IID_ICorDebugEnum) + { + *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugChainEnum *>(this)); + } + else if (id == IID_IUnknown) + { + *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugChainEnum *>(this)); + } + else + { + *ppInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +// Skip the specified number of chains. +HRESULT ShimChainEnum::Skip(ULONG celt) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + + // increment the index by the specified amount + m_currentChainIndex += celt; + return S_OK; +} + +HRESULT ShimChainEnum::Reset() +{ + m_currentChainIndex = 0; + return S_OK; +} + +// Clone the chain enumerator and set the new one to the same current chain +HRESULT ShimChainEnum::Clone(ICorDebugEnum ** ppEnum) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **); + + HRESULT hr = S_OK; + EX_TRY + { + NewHolder<ShimChainEnum> pChainEnum(new ShimChainEnum(m_pStackWalk, m_pShimLock)); + + // set the index in the new enumerator + pChainEnum->m_currentChainIndex = this->m_currentChainIndex; + + *ppEnum = pChainEnum; + (*ppEnum)->AddRef(); + m_pStackWalk->AddChainEnum(pChainEnum); + + pChainEnum.SuppressRelease(); + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// Return the number of chains on the thread +HRESULT ShimChainEnum::GetCount(ULONG * pcChains) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pcChains, ULONG *); + + *pcChains = m_pStackWalk->GetChainCount(); + return S_OK; +} + +// Retrieve the next x number of chains on the thread into "chains", where x is specified by "celt". +// "pcChainsFetched" is set to be the actual number of chains retrieved. +// Return S_FALSE if the number of chains actually retrieved is less than the number of chains requested. +HRESULT ShimChainEnum::Next(ULONG cChains, ICorDebugChain * rgpChains[], ULONG * pcChainsFetched) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpChains, ICorDebugChain *, cChains, true, true); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcChainsFetched, ULONG *); + + // if the out parameter is NULL, then we can only return one chain at a time + if ((pcChainsFetched == NULL) && (cChains != 1)) + { + return E_INVALIDARG; + } + + // Check for the trivial case where no chain is actually requested. + // This is probably a user error. + if (cChains == 0) + { + if (pcChainsFetched != NULL) + { + *pcChainsFetched = 0; + } + return S_OK; + } + + ICorDebugChain ** ppCurrentChain = rgpChains; + + while ((m_currentChainIndex < m_pStackWalk->GetChainCount()) && + (cChains > 0)) + { + *ppCurrentChain = m_pStackWalk->GetChain(m_currentChainIndex); + (*ppCurrentChain)->AddRef(); + + ppCurrentChain++; // increment the pointer into the buffer + m_currentChainIndex++; // increment the index + cChains--; + } + + // set the number of chains actually returned + if (pcChainsFetched != NULL) + { + *pcChainsFetched = (ULONG)(ppCurrentChain - rgpChains); + } + + // + // If we reached the end of the enumeration, but not the end + // of the number of requested items, we return S_FALSE. + // + if (cChains > 0) + { + return S_FALSE; + } + + return S_OK; +} + +ShimChainEnum * ShimChainEnum::GetNext() +{ + return m_pNext; +} + +void ShimChainEnum::SetNext(ShimChainEnum * pNext) +{ + if (m_pNext != NULL) + { + m_pNext->Release(); + } + + m_pNext = pNext; + + if (m_pNext != NULL) + { + m_pNext->AddRef(); + } +} + + +ShimFrameEnum::ShimFrameEnum(ShimStackWalk * pSW, + ShimChain * pChain, + UINT32 frameStartIndex, + UINT32 frameEndIndex, + RSLock * pShimLock) + : m_pStackWalk(pSW), + m_pChain(pChain), + m_pShimLock(pShimLock), + m_pNext(NULL), + m_currentFrameIndex(frameStartIndex), + m_endFrameIndex(frameEndIndex), + m_refCount(0), + m_fIsNeutered(FALSE) +{ +} + +ShimFrameEnum::~ShimFrameEnum() +{ + _ASSERTE(IsNeutered()); +} + +void ShimFrameEnum::Neuter() +{ + if (IsNeutered()) + { + return; + } + + m_fIsNeutered = TRUE; +} + +BOOL ShimFrameEnum::IsNeutered() +{ + return m_fIsNeutered; +} + + +ULONG STDMETHODCALLTYPE ShimFrameEnum::AddRef() +{ + return InterlockedIncrement((LONG *)&m_refCount); +} + +ULONG STDMETHODCALLTYPE ShimFrameEnum::Release() +{ + LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount); + _ASSERTE(newRefCount >= 0); + + if (newRefCount == 0) + { + delete this; + } + return newRefCount; +} + +HRESULT ShimFrameEnum::QueryInterface(REFIID id, void ** ppInterface) +{ + if (id == IID_ICorDebugFrameEnum) + { + *ppInterface = static_cast<ICorDebugFrameEnum *>(this); + } + else if (id == IID_ICorDebugEnum) + { + *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugFrameEnum *>(this)); + } + else if (id == IID_IUnknown) + { + *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugFrameEnum *>(this)); + } + else + { + *ppInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +// Skip the specified number of chains. +HRESULT ShimFrameEnum::Skip(ULONG celt) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + + // increment the index by the specified amount + m_currentFrameIndex += celt; + return S_OK; +} + +HRESULT ShimFrameEnum::Reset() +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + + m_currentFrameIndex = m_pChain->GetFirstFrameIndex(); + return S_OK; +} + +// Clone the chain enumerator and set the new one to the same current chain +HRESULT ShimFrameEnum::Clone(ICorDebugEnum ** ppEnum) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **); + + HRESULT hr = S_OK; + EX_TRY + { + NewHolder<ShimFrameEnum> pFrameEnum(new ShimFrameEnum(m_pStackWalk, + m_pChain, + m_currentFrameIndex, + m_endFrameIndex, + m_pShimLock)); + + *ppEnum = pFrameEnum; + (*ppEnum)->AddRef(); + m_pStackWalk->AddFrameEnum(pFrameEnum); + + pFrameEnum.SuppressRelease(); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +// Return the number of chains on the thread +HRESULT ShimFrameEnum::GetCount(ULONG * pcFrames) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pcFrames, ULONG *); + + *pcFrames = m_pChain->GetLastFrameIndex() - m_pChain->GetFirstFrameIndex(); + return S_OK; +} + +// Retrieve the next x number of chains on the thread into "chains", where x is specified by "celt". +// "pcChainsFetched" is set to be the actual number of chains retrieved. +// Return S_FALSE if the number of chains actually retrieved is less than the number of chains requested. +HRESULT ShimFrameEnum::Next(ULONG cFrames, ICorDebugFrame * rgpFrames[], ULONG * pcFramesFetched) +{ + RSLockHolder lockHolder(m_pShimLock); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpFrames, ICorDebugFrame *, cFrames, true, true); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcFramesFetched, ULONG *); + + // if the out parameter is NULL, then we can only return one chain at a time + if ((pcFramesFetched == NULL) && (cFrames != 1)) + { + return E_INVALIDARG; + } + + // Check for the trivial case where no chain is actually requested. + // This is probably a user error. + if (cFrames == 0) + { + if (pcFramesFetched != NULL) + { + *pcFramesFetched = 0; + } + return S_OK; + } + + ICorDebugFrame ** ppCurrentFrame = rgpFrames; + + while ((m_currentFrameIndex < m_endFrameIndex) && + (cFrames > 0)) + { + *ppCurrentFrame = m_pStackWalk->GetFrame(m_currentFrameIndex); + (*ppCurrentFrame)->AddRef(); + + ppCurrentFrame++; // increment the pointer into the buffer + m_currentFrameIndex++; // increment the index + cFrames--; + } + + // set the number of chains actually returned + if (pcFramesFetched != NULL) + { + *pcFramesFetched = (ULONG)(ppCurrentFrame - rgpFrames); + } + + // + // If we reached the end of the enumeration, but not the end + // of the number of requested items, we return S_FALSE. + // + if (cFrames > 0) + { + return S_FALSE; + } + + return S_OK; +} + +ShimFrameEnum * ShimFrameEnum::GetNext() +{ + return m_pNext; +} + +void ShimFrameEnum::SetNext(ShimFrameEnum * pNext) +{ + if (m_pNext != NULL) + { + m_pNext->Release(); + } + + m_pNext = pNext; + + if (m_pNext != NULL) + { + m_pNext->AddRef(); + } +} |