summaryrefslogtreecommitdiff
path: root/src/debug/di/rsstackwalk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/rsstackwalk.cpp')
-rw-r--r--src/debug/di/rsstackwalk.cpp823
1 files changed, 823 insertions, 0 deletions
diff --git a/src/debug/di/rsstackwalk.cpp b/src/debug/di/rsstackwalk.cpp
new file mode 100644
index 0000000000..08dd5e5d8c
--- /dev/null
+++ b/src/debug/di/rsstackwalk.cpp
@@ -0,0 +1,823 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+//
+// RsStackWalk.cpp
+//
+
+//
+// This file contains the implementation of the V3 managed stackwalking API.
+//
+// ======================================================================================
+
+#include "stdafx.h"
+#include "primitives.h"
+
+
+//---------------------------------------------------------------------------------------
+//
+// Constructor for CordbStackWalk.
+//
+// Arguments:
+// pCordbThread - the thread on which this stackwalker is created
+//
+
+CordbStackWalk::CordbStackWalk(CordbThread * pCordbThread)
+ : CordbBase(pCordbThread->GetProcess(), 0, enumCordbStackWalk),
+ m_pCordbThread(pCordbThread),
+ m_pSFIHandle(NULL),
+ m_cachedSetContextFlag(SET_CONTEXT_FLAG_ACTIVE_FRAME),
+ m_cachedHR(S_OK),
+ m_fIsOneFrameAhead(false)
+{
+ m_pCachedFrame.Clear();
+}
+
+void CordbStackWalk::Init()
+{
+ CordbProcess * pProcess = GetProcess();
+ m_lastSyncFlushCounter = pProcess->m_flushCounter;
+
+ IDacDbiInterface * pDAC = pProcess->GetDAC();
+ pDAC->CreateStackWalk(m_pCordbThread->m_vmThreadToken,
+ &m_context,
+ &m_pSFIHandle);
+
+ // see the function header of code:CordbStackWalk::CheckForLegacyHijackCase
+ CheckForLegacyHijackCase();
+
+ // Add itself to the neuter list.
+ m_pCordbThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+}
+
+// ----------------------------------------------------------------------------
+// CordbStackWalk::CheckForLegacyHijackCase
+//
+// Description:
+// @dbgtodo legacy interop debugging - In the case of an unhandled hardware exception, the
+// thread will be hijacked to code:Debugger::GenericHijackFunc, which the stackwalker doesn't know how to
+// unwind. We can teach the stackwalker to recognize that hijack stub, but since it's going to be deprecated
+// anyway, it's not worth the effort. So we check for the hijack CONTEXT here and use it as the CONTEXT. This
+// check should be removed when we are completely
+// out-of-process.
+//
+
+void CordbStackWalk::CheckForLegacyHijackCase()
+{
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ CordbProcess * pProcess = GetProcess();
+
+ // Only do this if we have a shim and we are interop-debugging.
+ if ((pProcess->GetShim() != NULL) &&
+ pProcess->IsInteropDebugging())
+ {
+ // And only if we have a CordbUnmanagedThread and we are hijacked to code:Debugger::GenericHijackFunc
+ CordbUnmanagedThread * pUT = pProcess->GetUnmanagedThread(m_pCordbThread->GetVolatileOSThreadID());
+ if (pUT != NULL)
+ {
+ if (pUT->IsFirstChanceHijacked() || pUT->IsGenericHijacked())
+ {
+ // The GetThreadContext function hides the effects of hijacking and returns the unhijacked context
+ m_context.ContextFlags = DT_CONTEXT_FULL;
+ pUT->GetThreadContext(&m_context);
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->SetStackWalkCurrentContext(m_pCordbThread->m_vmThreadToken,
+ m_pSFIHandle,
+ SET_CONTEXT_FLAG_ACTIVE_FRAME,
+ &m_context);
+ }
+ }
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Destructor for CordbStackWalk.
+//
+// Notes:
+// We don't really need to do anything here since the CordbStackWalk should have been neutered already.
+//
+
+CordbStackWalk::~CordbStackWalk()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function resets all the state on a CordbStackWalk and releases all the memory.
+// It is used for neutering and refreshing.
+//
+
+void CordbStackWalk::DeleteAll()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // delete allocated memory
+ if (m_pSFIHandle)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // For Mac debugging, it's not safe to call into the DAC once
+ // code:INativeEventPipeline::TerminateProcess is called. This is because the transport will not
+ // work anymore. The sole purpose of calling DeleteStackWalk() is to release the resources and
+ // memory allocated for the stackwalk. In the remote debugging case, the memory is allocated in
+ // the debuggee process. If the process is already terminated, then it's ok to skip the call.
+ if (!GetProcess()->m_exiting)
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ {
+ // This Delete call shouldn't actually throw. Worst case, the DDImpl leaked memory.
+ GetProcess()->GetDAC()->DeleteStackWalk(m_pSFIHandle);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ m_pSFIHandle = NULL;
+ }
+
+ // clear out the cached frame
+ m_pCachedFrame.Clear();
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Release all memory used by the stackwalker.
+//
+//
+// Notes:
+// CordbStackWalk is neutered by CordbThread or CleanupStack().
+//
+
+void CordbStackWalk::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ DeleteAll();
+ CordbBase::Neuter();
+}
+
+// standard QI function
+HRESULT CordbStackWalk::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugStackWalk)
+ {
+ *pInterface = static_cast<ICorDebugStackWalk*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugStackWalk*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refreshes all the state stored on the CordbStackWalk. This is necessary because sending IPC events to
+// the LS flushes the DAC cache, and m_pSFIHandle is allocated entirely in DAC memory. So, we keep track
+// of whether we have sent an IPC event and refresh the CordbStackWalk if necessary.
+//
+// Notes:
+// Throws on error.
+//
+
+void CordbStackWalk::RefreshIfNeeded()
+{
+ CordbProcess * pProcess = GetProcess();
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ // check if we need to refresh
+ if (m_lastSyncFlushCounter != pProcess->m_flushCounter)
+ {
+ // Make a local copy of the CONTEXT here. DeleteAll() will delete the CONTEXT on the cached frame,
+ // and CreateStackWalk() actually uses the CONTEXT buffer we pass to it.
+ DT_CONTEXT ctx;
+ if (m_fIsOneFrameAhead)
+ {
+ ctx = *(m_pCachedFrame->GetContext());
+ }
+ else
+ {
+ ctx = m_context;
+ }
+
+ // clear all the state
+ DeleteAll();
+
+ // create a new stackwalk handle
+ pProcess->GetDAC()->CreateStackWalk(m_pCordbThread->m_vmThreadToken,
+ &m_context,
+ &m_pSFIHandle);
+
+ // advance the stackwalker to where we originally were
+ SetContextWorker(m_cachedSetContextFlag, sizeof(DT_CONTEXT), reinterpret_cast<BYTE *>(&ctx));
+
+ // update the sync counter
+ m_lastSyncFlushCounter = pProcess->m_flushCounter;
+ }
+} // CordbStackWalk::RefreshIfNeeded()
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieves the CONTEXT of the current frame.
+//
+// Arguments:
+// contextFlags - context flags used to determine the required size for the buffer
+// contextBufSize - size of the CONTEXT buffer
+// pContextSize - out parameter; returns the size required for the CONTEXT buffer
+// pbContextBuf - the CONTEXT buffer
+//
+// Return Value:
+// Return S_OK on success.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack.
+// Return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the buffer is too small.
+// Return E_FAIL on other failures.
+//
+
+HRESULT CordbStackWalk::GetContext(ULONG32 contextFlags,
+ ULONG32 contextBufSize,
+ ULONG32 * pContextSize,
+ BYTE pbContextBuf[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+
+ // set the required size for the CONTEXT buffer
+ if (pContextSize != NULL)
+ {
+ *pContextSize = ContextSizeForFlags(contextFlags);
+ }
+
+ // If all the user wants to know is the CONTEXT size, then we are done.
+ if ((contextBufSize != 0) && (pbContextBuf != NULL))
+ {
+ if (contextBufSize < 4)
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ DT_CONTEXT * pContext = reinterpret_cast<DT_CONTEXT *>(pbContextBuf);
+
+ // Some helper functions that examine the context expect the flags to be initialized.
+ pContext->ContextFlags = contextFlags;
+
+ // check the size of the incoming buffer
+ if (!CheckContextSizeForBuffer(contextBufSize, pbContextBuf))
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // Check if we are one frame ahead. If so, returned the CONTEXT on the cached frame.
+ if (m_fIsOneFrameAhead)
+ {
+ if (m_pCachedFrame != NULL)
+ {
+ const DT_CONTEXT * pSrcContext = m_pCachedFrame->GetContext();
+ _ASSERTE(pSrcContext);
+ CORDbgCopyThreadContext(pContext, pSrcContext);
+ }
+ else
+ {
+ // We encountered a problem when we were trying to initialize the CordbNativeFrame.
+ // However, the problem occurred after we have unwound the current frame.
+ // What do we do here? We don't have the CONTEXT anymore.
+ _ASSERTE(FAILED(m_cachedHR));
+ ThrowHR(m_cachedHR);
+ }
+ }
+ else
+ {
+ // No easy way out in this case. We have to call the DDI.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+
+ IDacDbiInterface::FrameType ft = pDAC->GetStackWalkCurrentFrameInfo(m_pSFIHandle, NULL);
+ if (ft == IDacDbiInterface::kInvalid)
+ {
+ ThrowHR(E_FAIL);
+ }
+ else if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+ else if (ft == IDacDbiInterface::kExplicitFrame)
+ {
+ ThrowHR(CORDBG_E_NO_CONTEXT_FOR_INTERNAL_FRAME);
+ }
+ else
+ {
+ // We always store the current CONTEXT, so just copy it into the buffer.
+ CORDbgCopyThreadContext(pContext, &m_context);
+ }
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Set the stackwalker to the specified CONTEXT.
+//
+// Arguments:
+// flag - context flags used to determine the size of the CONTEXT
+// contextSize - the size of the CONTEXT
+// context - the CONTEXT as a byte array
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if context is NULL
+// Return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the CONTEXT is too small.
+// Return E_FAIL on other failures.
+//
+
+HRESULT CordbStackWalk::SetContext(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+ SetContextWorker(flag, contextSize, context);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbStackWalk::SetContext
+//
+
+void CordbStackWalk::SetContextWorker(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[])
+{
+ if (context == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ if (!CheckContextSizeForBuffer(contextSize, context))
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // invalidate the cache
+ m_pCachedFrame.Clear();
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+
+ DT_CONTEXT * pSrcContext = reinterpret_cast<DT_CONTEXT *>(context);
+
+ // Check the incoming CONTEXT using a temporary CONTEXT buffer before updating our real CONTEXT buffer.
+ // The incoming CONTEXT is not required to have all the bits set in its CONTEXT flags, so only update
+ // the registers specified by the CONTEXT flags. Note that CORDbgCopyThreadContext() honours the CONTEXT
+ // flags on both the source and the destination CONTEXTs when it copies them.
+ DT_CONTEXT tmpCtx = m_context;
+ tmpCtx.ContextFlags |= pSrcContext->ContextFlags;
+ CORDbgCopyThreadContext(&tmpCtx, pSrcContext);
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IfFailThrow(pDAC->CheckContext(m_pCordbThread->m_vmThreadToken, &tmpCtx));
+
+ // At this point we have done all of our checks to verify that the incoming CONTEXT is sane, so we can
+ // update our internal CONTEXT buffer.
+ m_context = tmpCtx;
+ m_cachedSetContextFlag = flag;
+
+ pDAC->SetStackWalkCurrentContext(m_pCordbThread->m_vmThreadToken,
+ m_pSFIHandle,
+ flag,
+ &m_context);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Helper to perform all the necessary operations when we unwind, including:
+// 1) Unwind
+// 2) Save the new unwound CONTEXT
+//
+// Return Value:
+// Return TRUE if we successfully unwind to the next frame.
+// Return FALSE if there is no more frame to walk.
+// Throw on error.
+//
+
+BOOL CordbStackWalk::UnwindStackFrame()
+{
+ CordbProcess * pProcess = GetProcess();
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ IDacDbiInterface * pDAC = pProcess->GetDAC();
+ BOOL retVal = pDAC->UnwindStackWalkFrame(m_pSFIHandle);
+
+ // Now that we have unwound, make sure we update the CONTEXT buffer to reflect the current stack frame.
+ // This call is safe regardless of whether the unwind is successful or not.
+ pDAC->GetStackWalkCurrentContext(m_pSFIHandle, &m_context);
+
+ return retVal;
+} // CordbStackWalk::UnwindStackWalkFrame
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stackwalker to the next frame.
+//
+// Return Value:
+// Return S_OK on success.
+// Return CORDBG_E_FAIL_TO_UNWIND_FRAME if the unwind fails.
+// Return CORDBG_S_AT_END_OF_STACK if we have reached the end of the stack as a result of this unwind.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack to begin with.
+//
+
+HRESULT CordbStackWalk::Next()
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+ if (m_fIsOneFrameAhead)
+ {
+ // We have already unwound to the next frame when we materialize the CordbNativeFrame
+ // for the current frame. So we just need to clear the cache because we are already at
+ // the next frame.
+ if (m_pCachedFrame != NULL)
+ {
+ m_pCachedFrame.Clear();
+ }
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+ }
+ else
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IDacDbiInterface::FrameType ft = IDacDbiInterface::kInvalid;
+
+ ft = pDAC->GetStackWalkCurrentFrameInfo(this->m_pSFIHandle, NULL);
+ if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+
+ // update the cahced flag to indicate that we have reached an unwind CONTEXT
+ m_cachedSetContextFlag = SET_CONTEXT_FLAG_UNWIND_FRAME;
+
+ if (UnwindStackFrame())
+ {
+ hr = S_OK;
+ }
+ else
+ {
+ hr = CORDBG_S_AT_END_OF_STACK;
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieves an ICDFrame corresponding to the current frame:
+// Stopped At Out Parameter Return Value
+// ---------- ------------- ------------
+// explicit frame CordbInternalFrame S_OK
+// managed stack frame CordbNativeFrame S_OK
+// native stack frame NULL S_FALSE
+//
+// Arguments:
+// ppFrame - out parameter; return the ICDFrame
+//
+// Return Value:
+// On success return the HRs above.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack.
+// Return E_INVALIDARG if ppFrame is NULL
+// Return E_FAIL on other errors.
+//
+// Notes:
+// This is just a wrapper with an EX_TRY/EX_CATCH_HRESULT for GetFrameWorker().
+//
+
+HRESULT CordbStackWalk::GetFrame(ICorDebugFrame ** ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_NO_LOCK_BEGIN(this)
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ RefreshIfNeeded();
+ hr = GetFrameWorker(ppFrame);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+
+ if (FAILED(hr))
+ {
+ if (m_fIsOneFrameAhead && (m_pCachedFrame == NULL))
+ {
+ // We encountered a problem when we try to materialize a CordbNativeFrame.
+ // Cache the failure HR so that we can return it later if the caller
+ // calls GetFrame() again or GetContext().
+ m_cachedHR = hr;
+ }
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbStackWalk::GetFrame
+//
+
+HRESULT CordbStackWalk::GetFrameWorker(ICorDebugFrame ** ppFrame)
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ if (ppFrame == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *ppFrame = NULL;
+
+ RSInitHolder<CordbFrame> pResultFrame(NULL);
+
+ if (m_fIsOneFrameAhead)
+ {
+ if (m_pCachedFrame != NULL)
+ {
+ pResultFrame.Assign(m_pCachedFrame);
+ pResultFrame.TransferOwnershipExternal(ppFrame);
+ return S_OK;
+ }
+ else
+ {
+ // We encountered a problem when we were trying to initialize the CordbNativeFrame.
+ // However, the problem occurred after we have unwound the current frame.
+ // Whatever error code we return, it should be the same one GetContext() returns.
+ _ASSERTE(FAILED(m_cachedHR));
+ ThrowHR(m_cachedHR);
+ }
+ }
+
+ IDacDbiInterface * pDAC = NULL;
+ DebuggerIPCE_STRData frameData;
+ ZeroMemory(&frameData, sizeof(frameData));
+ IDacDbiInterface::FrameType ft = IDacDbiInterface::kInvalid;
+
+ pDAC = GetProcess()->GetDAC();
+ ft = pDAC->GetStackWalkCurrentFrameInfo(m_pSFIHandle, &frameData);
+
+ if (ft == IDacDbiInterface::kInvalid)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - invalid stackwalker (%p)", this);
+ ThrowHR(E_FAIL);
+ }
+ else if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - past end of stack (%p)", this);
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+ else if (ft == IDacDbiInterface::kNativeStackFrame)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - native stack frame (%p)", this);
+ return S_FALSE;
+ }
+ else if (ft == IDacDbiInterface::kExplicitFrame)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - explicit frame (%p)", this);
+
+ // We no longer expect to get internal frames by unwinding.
+ GetProcess()->TargetConsistencyCheck(false);
+ }
+ else if (ft == IDacDbiInterface::kManagedStackFrame)
+ {
+ _ASSERTE(frameData.eType == DebuggerIPCE_STRData::cMethodFrame);
+
+ HRESULT hr = S_OK;
+
+ // In order to find the FramePointer on x86, we need to unwind to the next frame.
+ // Technically, only x86 needs to do this, because the x86 runtime stackwalker doesn't uwnind
+ // one frame ahead of time. However, we are doing this on all platforms to keep things simple.
+ BOOL fSuccess = UnwindStackFrame();
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_fIsOneFrameAhead = true;
+#if defined(DBG_TARGET_X86)
+ frameData.fp = pDAC->GetFramePointer(m_pSFIHandle);
+#endif // DBG_TARGET_X86
+
+ // currentFuncData contains general information about the method.
+ // It has no information about any particular jitted instance of the method.
+ DebuggerIPCE_FuncData * pFuncData = &(frameData.v.funcData);
+
+ // currentJITFuncData contains information about the current jitted instance of the method
+ // on the stack.
+ DebuggerIPCE_JITFuncData * pJITFuncData = &(frameData.v.jitFuncData);
+
+ // Lookup the appdomain that the thread was in when it was executing code for this frame. We pass this
+ // to the frame when we create it so we can properly resolve locals in that frame later.
+ CordbAppDomain * pCurrentAppDomain = GetProcess()->LookupOrCreateAppDomain(frameData.vmCurrentAppDomainToken);
+ _ASSERTE(pCurrentAppDomain != NULL);
+
+ // Lookup the module
+ CordbModule* pModule = pCurrentAppDomain->LookupOrCreateModule(pFuncData->vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ // Create or look up a CordbNativeCode. There is one for each jitted instance of a method,
+ // and we may have multiple instances because of generics.
+ CordbNativeCode * pNativeCode = pModule->LookupOrCreateNativeCode(pFuncData->funcMetadataToken,
+ pJITFuncData->vmNativeCodeMethodDescToken,
+ pJITFuncData->nativeStartAddressPtr);
+ IfFailThrow(hr);
+
+ // The native code object will create the function object if needed
+ CordbFunction * pFunction = pNativeCode->GetFunction();
+
+ // A CordbFunction is theoretically the uninstantiated method, yet for back-compat we allow
+ // debuggers to assume that it corresponds to exactly 1 native code blob. In order for
+ // an open generic function to know what native code to give back, we attach an arbitrary
+ // native code that we located through code inspection.
+ // Note that not all CordbFunction objects get created via stack traces because you can also
+ // create them by name. In that case you still won't get code for Open generic functions
+ // because we will never have attached one and the lookup by token is insufficient. This
+ // behavior mimics our 2.0 debugging behavior though so its not a regression.
+ pFunction->NotifyCodeCreated(pNativeCode);
+
+ IfFailThrow(hr);
+
+ _ASSERTE((pFunction != NULL) && (pNativeCode != NULL));
+
+ // initialize the auxiliary info required for funclets
+ CordbMiscFrame miscFrame(pJITFuncData);
+
+ // Create the native frame.
+ CordbNativeFrame* pNativeFrame = new CordbNativeFrame(m_pCordbThread,
+ frameData.fp,
+ pNativeCode,
+ pJITFuncData->nativeOffset,
+ &(frameData.rd),
+ frameData.v.taAmbientESP,
+ !!frameData.quicklyUnwound,
+ pCurrentAppDomain,
+ &miscFrame,
+ &(frameData.ctx));
+
+ pResultFrame.Assign(static_cast<CordbFrame *>(pNativeFrame));
+ m_pCachedFrame.Assign(static_cast<CordbFrame *>(pNativeFrame));
+
+ // @dbgtodo dynamic language debugging
+ // If we are dealing with a dynamic method (e.g. an IL stub, a LCG method, etc.),
+ // then we don't have the metadata or the debug info (sequence points, etc.).
+ // This means that we can't do anything meaningful with a CordbJITILFrame anyway,
+ // so let's not create the CordbJITILFrame at all. Note that methods created with
+ // RefEmit are okay, i.e. they have metadata.
+
+ // The check for IsNativeImpl() != CordbFunction::kNativeOnly catches an odd profiler
+ // case. A profiler can rewrite assemblies at load time so that a P/invoke becomes a
+ // regular managed method. mscordbi isn't yet designed to handle runtime metadata
+ // changes, so it still thinks the method is a p/invoke. If we only relied on
+ // frameData.v.fNoMetadata which is populated by the DAC, that will report
+ // FALSE (the method does have metadata/IL now). However pNativeCode->LoadNativeInfo
+ // is going to check DBI's metadata and calculate this is a p/invoke, which will
+ // throw an exception that the method isn't IL.
+ // Ideally we probably want to expose the profiler's change to the method,
+ // however that will take significant work. Part of that is correctly detecting and
+ // updating metadata in DBI, part is determinging if/how the debugger is notified,
+ // and part is auditing mscordbi to ensure that anything we cached based on the
+ // old metadata is correctly invalidated.
+ // Since this is a late fix going into a controlled servicing release I have
+ // opted for a much narrower fix. Doing the check for IsNativeImpl() != CordbFunction::kNativeOnly
+ // will continue to treat our new method as though it was a p/invoke, and the
+ // debugger will not provide IL for it. The debugger can't inspect within the profiler
+ // modified method, but at least the error won't leak out to interfere with inspection
+ // of the callstack as a whole.
+ if (!frameData.v.fNoMetadata &&
+ pNativeCode->GetFunction()->IsNativeImpl() != CordbFunction::kNativeOnly)
+ {
+ pNativeCode->LoadNativeInfo();
+
+ // By design, when a managed exception occurs we return the sequence point containing the faulting
+ // instruction in the leaf frame. In the past we didn't always achieve this,
+ // but we are being more deliberate about this behavior now.
+
+ // If jsutAfterILThrow is true, it means nativeOffset points to the return address of IL_Throw
+ // (or another JIT exception helper) after an exception has been thrown.
+ // In such cases we want to adjust nativeOffset, so it will point an actual exception callsite.
+ // By subtracting STACKWALK_CONTROLPC_ADJUST_OFFSET from nativeOffset you can get
+ // an address somewhere inside CALL instruction.
+ // This ensures more consistent placement of exception line highlighting in Visual Studio
+ DWORD nativeOffsetToMap = pJITFuncData->jsutAfterILThrow ?
+ (DWORD)pJITFuncData->nativeOffset - STACKWALK_CONTROLPC_ADJUST_OFFSET :
+ (DWORD)pJITFuncData->nativeOffset;
+ CorDebugMappingResult mappingType;
+ ULONG uILOffset = pNativeCode->GetSequencePoints()->MapNativeOffsetToIL(
+ nativeOffsetToMap,
+ &mappingType);
+
+ // Find or create the IL Code, and the pJITILFrame.
+ RSExtSmartPtr<CordbILCode> pCode;
+
+ // The code for populating CordbFunction ILCode looks really bizzare... it appears to only grab the
+ // correct version of the IL if that is still the current EnC version yet it is populated deliberately
+ // late bound at which point the latest version may be different. In fact even here the latest version
+ // could already be different, but this is no worse than what the code used to do
+ hr = pFunction->GetILCode(&pCode);
+ IfFailThrow(hr);
+ _ASSERTE(pCode != NULL);
+
+ // We populate the code for ReJit eagerly to make sure we still have it if the profiler removes the
+ // instrumentation later. Of course the only way it will still be accesible to our caller is if he
+ // saves a pointer to the ILCode.
+ // I'm not sure if ignoring rejit for mini-dumps is the right call long term, but we aren't doing
+ // anything special to collect the memory at dump time so we better be prepared to not fetch it here.
+ // We'll attempt to treat it as not being instrumented, though I suspect the abstraction is leaky.
+ RSSmartPtr<CordbReJitILCode> pReJitCode;
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ VMPTR_ReJitInfo reJitInfo = VMPTR_ReJitInfo::NullPtr();
+ IfFailThrow(GetProcess()->GetDAC()->GetReJitInfo(pJITFuncData->vmNativeCodeMethodDescToken, pJITFuncData->nativeStartAddressPtr, &reJitInfo));
+ if (!reJitInfo.IsNull())
+ {
+ VMPTR_SharedReJitInfo sharedReJitInfo = VMPTR_SharedReJitInfo::NullPtr();
+ IfFailThrow(GetProcess()->GetDAC()->GetSharedReJitInfo(reJitInfo, &sharedReJitInfo));
+ IfFailThrow(pFunction->LookupOrCreateReJitILCode(sharedReJitInfo, &pReJitCode));
+ }
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+
+
+ RSInitHolder<CordbJITILFrame> pJITILFrame(new CordbJITILFrame(pNativeFrame,
+ pCode,
+ uILOffset,
+ mappingType,
+ frameData.v.exactGenericArgsToken,
+ frameData.v.dwExactGenericArgsTokenIndex,
+ !!frameData.v.fVarArgs,
+ pReJitCode));
+
+ // Initialize the frame. This is a nop if the method is not a vararg method.
+ hr = pJITILFrame->Init();
+ IfFailThrow(hr);
+
+ pNativeFrame->m_JITILFrame.Assign(pJITILFrame);
+ pJITILFrame.ClearAndMarkDontNeuter();
+ }
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "CSW::GFW - managed stack frame (%p): CNF - 0x%p, CJILF - 0x%p",
+ this, pNativeFrame, pNativeFrame->m_JITILFrame.GetValue());
+ } // kManagedStackFrame
+ else if (ft == IDacDbiInterface::kNativeRuntimeUnwindableStackFrame)
+ {
+ _ASSERTE(frameData.eType == DebuggerIPCE_STRData::cRuntimeNativeFrame);
+
+ // In order to find the FramePointer on x86, we need to unwind to the next frame.
+ // Technically, only x86 needs to do this, because the x86 runtime stackwalker doesn't uwnind
+ // one frame ahead of time. However, we are doing this on all platforms to keep things simple.
+ BOOL fSuccess = UnwindStackFrame();
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_fIsOneFrameAhead = true;
+#if defined(DBG_TARGET_X86)
+ frameData.fp = pDAC->GetFramePointer(m_pSFIHandle);
+#endif // DBG_TARGET_X86
+
+ // Lookup the appdomain that the thread was in when it was executing code for this frame. We pass this
+ // to the frame when we create it so we can properly resolve locals in that frame later.
+ CordbAppDomain * pCurrentAppDomain =
+ GetProcess()->LookupOrCreateAppDomain(frameData.vmCurrentAppDomainToken);
+ _ASSERTE(pCurrentAppDomain != NULL);
+
+ CordbRuntimeUnwindableFrame * pRuntimeFrame = new CordbRuntimeUnwindableFrame(m_pCordbThread,
+ frameData.fp,
+ pCurrentAppDomain,
+ &(frameData.ctx));
+
+ pResultFrame.Assign(static_cast<CordbFrame *>(pRuntimeFrame));
+ m_pCachedFrame.Assign(static_cast<CordbFrame *>(pRuntimeFrame));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CSW::GFW - runtime unwindable stack frame (%p): 0x%p",
+ this, pRuntimeFrame);
+ }
+
+ pResultFrame.TransferOwnershipExternal(ppFrame);
+
+ return S_OK;
+}