Reimplement stack walking similar to Mdbg
authorIgor Kulaychuk <i.kulaychuk@samsung.com>
Tue, 1 Aug 2017 13:45:33 +0000 (16:45 +0300)
committerIgor Kulaychuk <i.kulaychuk@samsung.com>
Mon, 13 Nov 2017 19:22:40 +0000 (22:22 +0300)
src/debug/netcoredbg/frames.cpp

index 402a3c5..4873a9d 100644 (file)
@@ -7,6 +7,7 @@
 #include <iomanip>
 #include <mutex>
 #include <condition_variable>
+#include <algorithm>
 
 #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<HRESULT(FrameType,ICorDebugFrame*,ICorDebugILFrame*,ICorDebugFunction*)> 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<NativeFrame> &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<HRESULT(FrameType,ICorDebugFrame*,NativeFrame*,ICorDebugFunction*)> WalkFramesCallback;
+
+HRESULT StitchInternalFrames(
+    ICorDebugThread *pThread,
+    const std::vector< ToRelease<ICorDebugFrame> > &internalFrameCache,
+    const std::vector<NativeFrame> &nativeFrames,
+    WalkFramesCallback cb)
+{
+    HRESULT Status;
+
+    struct FrameIndex {
+        size_t index;
+        bool internal;
+        uint64_t addr;
+
+        FrameIndex(size_t ind, const ToRelease<ICorDebugFrame> &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<FrameIndex> 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<NativeFrame*>(&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<ICorDebugFrame> > 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(&currentCtx, 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(&currentCtx, 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<ICorDebugRuntimeUnwindableFrame> pRuntimeUnwindableFrame;
         Status = pFrame->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (LPVOID *) &pRuntimeUnwindableFrame);
         if (SUCCEEDED(Status))
         {
-            IfFailRet(cb(FrameRuntimeUnwindable, pFrame, nullptr, nullptr));
             continue;
         }
 
-        ToRelease<ICorDebugILFrame> 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<ICorDebugInternalFrame> 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(&currentCtx, 0, sizeof(CONTEXT));
+            ULONG32 contextSize;
+            IfFailRet(pStackWalk->GetContext(ctxFlags, sizeof(CONTEXT), &contextSize, (BYTE*) &currentCtx));
+            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<uint64_t>::max();
+        if (currentCtxValid)
+        {
+            pEndVal = GetSP(&currentCtx);
+        }
+
+        // check to see if we have native frames to unwind
+        std::vector<NativeFrame> 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<ICorDebugFunction> 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<ICorDebugNativeFrame> 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<NativeFrame> 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<uint64_t>::max();
+    std::vector<NativeFrame> 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<ICorDebugInternalFrame> 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);