From ade214eb551b44743e58fa618d8c94b9ede685f7 Mon Sep 17 00:00:00 2001 From: Igor Kulaychuk Date: Tue, 1 Aug 2017 16:45:33 +0300 Subject: [PATCH] Reimplement stack walking similar to Mdbg --- src/debug/netcoredbg/frames.cpp | 271 +++++++++++++++++++++++++++++++++++----- 1 file changed, 238 insertions(+), 33 deletions(-) diff --git a/src/debug/netcoredbg/frames.cpp b/src/debug/netcoredbg/frames.cpp index 402a3c5..4873a9d 100644 --- a/src/debug/netcoredbg/frames.cpp +++ b/src/debug/netcoredbg/frames.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "typeprinter.h" #include "platform.h" @@ -67,6 +68,21 @@ HRESULT PrintThreadsState(ICorDebugController *controller, std::string &output) return S_OK; } +static std::string AddrToString(uint64_t addr) +{ + std::stringstream ss; + ss << "0x" << std::setw(2 * sizeof(void*)) << std::setfill('0') << std::hex << addr; + return ss.str(); +} + +static std::string FrameAddrToString(ICorDebugFrame *pFrame) +{ + CORDB_ADDRESS startAddr = 0; + CORDB_ADDRESS endAddr = 0; + pFrame->GetStackRange(&startAddr, &endAddr); + return AddrToString(startAddr); +} + HRESULT PrintFrameLocation(ICorDebugFrame *pFrame, std::string &output) { HRESULT Status; @@ -118,29 +134,104 @@ HRESULT PrintFrameLocation(ICorDebugFrame *pFrame, std::string &output) std::string methodName; TypePrinter::GetMethodName(pFrame, methodName); ss << "func=\"" << methodName << "\","; - - CORDB_ADDRESS startAddr = 0; - CORDB_ADDRESS endAddr = 0; - pFrame->GetStackRange(&startAddr, &endAddr); - - ss << "addr=\"0x" << std::setw(sizeof(void*)) << std::setfill('0') << std::hex << startAddr << "\""; + ss << "addr=\"" << FrameAddrToString(pFrame) << "\""; output = ss.str(); return has_source ? S_OK : S_FALSE; } +struct NativeFrame +{ + uint64_t addr; + std::string symbol; + std::string file; + std::string fullname; + int linenum; + int tid; + NativeFrame() : addr(0), linenum(0), tid(0) {} +}; + enum FrameType { - FrameNative, - FrameRuntimeUnwindable, - FrameILStubOrLCG, FrameUnknown, - FrameManaged + FrameNative, + FrameCLRNative, + FrameCLRInternal, + FrameCLRManaged }; -typedef std::function WalkFramesCallback; +static uint64_t GetSP(CONTEXT *context) +{ +#if defined(_TARGET_AMD64_) + return (uint64_t)(size_t)context->Rsp; +#elif defined(_TARGET_X86_) + return (uint64_t)(size_t)context->Esp; +#elif defined(_TARGET_ARM_) + return (uint64_t)(size_t)context->Sp; +#elif defined(_TARGET_ARM64_) + return (uint64_t)(size_t)context->Sp; +#elif +#error "Unsupported platform" +#endif +} + +HRESULT UnwindNativeFrames(ICorDebugThread *pThread, uint64_t startValue, uint64_t endValue, std::vector &frames) +{ + return S_OK; +} + +static uint64_t GetFrameAddr(ICorDebugFrame *pFrame) +{ + CORDB_ADDRESS startAddr = 0; + CORDB_ADDRESS endAddr = 0; + pFrame->GetStackRange(&startAddr, &endAddr); + return startAddr; +} + +typedef std::function WalkFramesCallback; + +HRESULT StitchInternalFrames( + ICorDebugThread *pThread, + const std::vector< ToRelease > &internalFrameCache, + const std::vector &nativeFrames, + WalkFramesCallback cb) +{ + HRESULT Status; + + struct FrameIndex { + size_t index; + bool internal; + uint64_t addr; + + FrameIndex(size_t ind, const ToRelease &frame) + : index(ind), internal(true), addr(GetFrameAddr(frame.GetPtr())) {} + FrameIndex(size_t ind, const NativeFrame &frame) + : index(ind), internal(false), addr(frame.addr) {} + }; + + std::vector frames; + + for (size_t i = 0; i < nativeFrames.size(); i++) frames.emplace_back(i, nativeFrames[i]); + for (size_t i = 0; i < internalFrameCache.size(); i++) frames.emplace_back(i, internalFrameCache[i]); + + std::sort(frames.begin( ), frames.end(), [](const FrameIndex& lhs, const FrameIndex& rhs) + { + return lhs.addr < rhs.addr; + }); + for (auto &fr : frames) + { + if (fr.internal) + IfFailRet(cb(FrameCLRInternal, internalFrameCache[fr.index].GetPtr(), nullptr, nullptr)); + else + IfFailRet(cb(FrameNative, nullptr, const_cast(&nativeFrames[fr.index]), nullptr)); + } + + return S_OK; +} + +// From https://github.com/SymbolSource/Microsoft.Samples.Debugging/blob/master/src/debugger/mdbgeng/FrameFactory.cs HRESULT WalkFrames(ICorDebugThread *pThread, WalkFramesCallback cb) { HRESULT Status; @@ -151,8 +242,22 @@ HRESULT WalkFrames(ICorDebugThread *pThread, WalkFramesCallback cb) IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3)); IfFailRet(pThread3->CreateStackWalk(&pStackWalk)); + std::vector< ToRelease > iFrameCache; + + static const ULONG32 ctxFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + CONTEXT ctxUnmanagedChain; + bool ctxUnmanagedChainValid = false; + CONTEXT currentCtx; + bool currentCtxValid = false; + memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT)); + memset(¤tCtx, 0, sizeof(CONTEXT)); + + int level = -1; + for (Status = S_OK; ; Status = pStackWalk->Next()) { + level++; + if (Status == CORDBG_S_AT_END_OF_STACK) break; @@ -162,38 +267,109 @@ HRESULT WalkFrames(ICorDebugThread *pThread, WalkFramesCallback cb) IfFailRet(pStackWalk->GetFrame(&pFrame)); if (Status == S_FALSE) { - IfFailRet(cb(FrameNative, pFrame, nullptr, nullptr)); + // we've hit a native frame, we need to store the CONTEXT + memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT)); + ULONG32 contextSize; + IfFailRet(pStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) &ctxUnmanagedChain)); + ctxUnmanagedChainValid = true; + // we need to invalidate the currentCtx since it won't be valid on the next loop iteration + memset(¤tCtx, 0, sizeof(CONTEXT)); + currentCtxValid = false; continue; } + // If we get a RuntimeUnwindableFrame, then the stackwalker is also stopped at a native + // stack frame, but it's a native stack frame which requires special unwinding help from + // the runtime. When a debugger gets a RuntimeUnwindableFrame, it should use the runtime + // to unwind, but it has to do inspection on its own. It can call + // ICorDebugStackWalk::GetContext() to retrieve the context of the native stack frame. ToRelease pRuntimeUnwindableFrame; Status = pFrame->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (LPVOID *) &pRuntimeUnwindableFrame); if (SUCCEEDED(Status)) { - IfFailRet(cb(FrameRuntimeUnwindable, pFrame, nullptr, nullptr)); continue; } - ToRelease pILFrame; - HRESULT hrILFrame = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame); - - if (FAILED(hrILFrame)) + // check for an internal frame...if the internal frame happens to come after the last + // managed frame, any call to GetContext() will assert + ToRelease pInternalFrame; + if (FAILED(pFrame->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID*) &pInternalFrame))) { - IfFailRet(cb(FrameUnknown, pFrame, nullptr, nullptr)); + // we need to store the CONTEXT when we're at a managed frame, if there's an internal frame + // after this, then we'll need this CONTEXT + memset(¤tCtx, 0, sizeof(CONTEXT)); + ULONG32 contextSize; + IfFailRet(pStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) ¤tCtx)); + currentCtxValid = true; + } + else if (ctxUnmanagedChainValid) + { + // we need to check to see if this internal frame could have been sandwiched between + // native frames, this will be the case if ctxUnmanagedChain is not null + + // we need to store ALL internal frames until we hit the next managed frame + iFrameCache.emplace_back(std::move(pFrame)); continue; } + // else we'll use the 'stored' currentCtx if we're at an InternalFrame + + uint64_t pEndVal = std::numeric_limits::max(); + if (currentCtxValid) + { + pEndVal = GetSP(¤tCtx); + } + + // check to see if we have native frames to unwind + std::vector nFrames; + if (ctxUnmanagedChainValid) + IfFailRet(UnwindNativeFrames(pThread, GetSP(&ctxUnmanagedChain), pEndVal, nFrames)); + IfFailRet(StitchInternalFrames(pThread, iFrameCache, nFrames, cb)); + // clear out the CONTEXT and native frame cache + memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT)); + ctxUnmanagedChainValid = false; + nFrames.clear(); + iFrameCache.clear(); + + // return the managed frame ToRelease pFunction; Status = pFrame->GetFunction(&pFunction); - if (FAILED(Status)) + if (SUCCEEDED(Status)) { - IfFailRet(cb(FrameILStubOrLCG, pFrame, pILFrame, nullptr)); + IfFailRet(cb(FrameCLRManaged, pFrame, nullptr, pFunction)); continue; } + // If we cannot get managed function then return internal or native frame + FrameType frameType = pInternalFrame ? FrameCLRInternal : FrameCLRNative; - IfFailRet(cb(FrameManaged, pFrame, pILFrame, pFunction)); + ToRelease pNativeFrame; + HRESULT hrNativeFrame = pFrame->QueryInterface(IID_ICorDebugNativeFrame, (LPVOID*) &pNativeFrame); + if (FAILED(hrNativeFrame)) + { + IfFailRet(cb(FrameUnknown, pFrame, nullptr, nullptr)); + continue; + } + // If the first frame is either internal or native then we might be in a call to unmanged code + if (level == 0) + { + std::vector nFrames; + IfFailRet(UnwindNativeFrames(pThread, 0, GetFrameAddr(pFrame), nFrames)); + IfFailRet(StitchInternalFrames(pThread, {}, nFrames, cb)); + } + IfFailRet(cb(frameType, pFrame, nullptr, nullptr)); } + // we may have native frames at the end of the stack + uint64_t pEndVal = std::numeric_limits::max(); + std::vector nFrames; + if (ctxUnmanagedChainValid) + IfFailRet(UnwindNativeFrames(pThread, GetSP(&ctxUnmanagedChain), pEndVal, nFrames)); + IfFailRet(StitchInternalFrames(pThread, iFrameCache, nFrames, cb)); + + nFrames.clear(); + memset(&ctxUnmanagedChain, 0, sizeof(CONTEXT)); + ctxUnmanagedChainValid = false; + return S_OK; } @@ -206,7 +382,7 @@ HRESULT GetFrameAt(ICorDebugThread *pThread, int level, ICorDebugFrame **ppFrame HRESULT Status = WalkFrames(pThread, [&]( FrameType frameType, ICorDebugFrame *pFrame, - ICorDebugILFrame *pILFrame, + NativeFrame *pNative, ICorDebugFunction *pFunction) { currentFrame++; @@ -216,7 +392,7 @@ HRESULT GetFrameAt(ICorDebugThread *pThread, int level, ICorDebugFrame **ppFrame else if (currentFrame > level) return E_FAIL; - if (currentFrame == level && frameType == FrameManaged) + if (currentFrame == level && frameType == FrameCLRManaged) { pFrame->AddRef(); result = pFrame; @@ -233,6 +409,24 @@ HRESULT GetFrameAt(ICorDebugThread *pThread, int level, ICorDebugFrame **ppFrame return Status; } +static const char *GetInternalTypeName(CorDebugInternalFrameType frameType) +{ + switch(frameType) + { + case STUBFRAME_M2U: return "Managed to Native Transition"; + case STUBFRAME_U2M: return "Native to Managed Transition"; + case STUBFRAME_APPDOMAIN_TRANSITION: return "Appdomain Transition"; + case STUBFRAME_LIGHTWEIGHT_FUNCTION: return "Lightweight function"; + case STUBFRAME_FUNC_EVAL: return "Func Eval"; + case STUBFRAME_INTERNALCALL: return "Internal Call"; + case STUBFRAME_CLASS_INIT: return "Class Init"; + case STUBFRAME_EXCEPTION: return "Exception"; + case STUBFRAME_SECURITY: return "Security"; + case STUBFRAME_JIT_COMPILATION: return "JIT Compilation"; + default: return "Unknown"; + } +} + HRESULT PrintFrames(ICorDebugThread *pThread, std::string &output, int lowFrame = 0, int highFrame = INT_MAX) { HRESULT Status; @@ -246,7 +440,7 @@ HRESULT PrintFrames(ICorDebugThread *pThread, std::string &output, int lowFrame IfFailRet(WalkFrames(pThread, [&]( FrameType frameType, ICorDebugFrame *pFrame, - ICorDebugILFrame *pILFrame, + NativeFrame *pNative, ICorDebugFunction *pFunction) { currentFrame++; @@ -261,19 +455,30 @@ HRESULT PrintFrames(ICorDebugThread *pThread, std::string &output, int lowFrame switch(frameType) { - case FrameNative: - ss << "frame={level=\"" << currentFrame << "\",func=\"[NativeStackFrame]\"}"; + case FrameUnknown: + ss << "frame={level=\"" << currentFrame << "\",func=\"?\"}"; break; - case FrameRuntimeUnwindable: - ss << "frame={level=\"" << currentFrame << "\",func=\"[RuntimeUnwindableFrame]\"}"; + case FrameNative: + ss << "frame={level=\"" << currentFrame << "\",func=\"" << pNative->symbol << "\"," + << "file=\"" << pNative->file << "\"," + << "line=\"" << pNative->linenum << "\"," + << "addr=\"" << AddrToString(pNative->addr) << "\"}"; break; - case FrameILStubOrLCG: - ss << "frame={level=\"" << currentFrame << "\",func=\"[IL Stub or LCG]\"}"; + case FrameCLRNative: + ss << "frame={level=\"" << currentFrame << "\",func=\"[Native Frame]\"," + << "addr=\"" << FrameAddrToString(pFrame) << "\"}"; break; - case FrameUnknown: - ss << "frame={level=\"" << currentFrame << "\",func=\"?\"}"; + case FrameCLRInternal: + { + ToRelease pInternalFrame; + IfFailRet(pFrame->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID*) &pInternalFrame)); + CorDebugInternalFrameType frameType; + IfFailRet(pInternalFrame->GetFrameType(&frameType)); + ss << "frame={level=\"" << currentFrame << "\",func=\"[" << GetInternalTypeName(frameType) << "]\"," + << "addr=\"" << FrameAddrToString(pFrame) << "\"}"; + } break; - case FrameManaged: + case FrameCLRManaged: { std::string frameLocation; PrintFrameLocation(pFrame, frameLocation); -- 2.7.4