Add Hot Reload aware reply for `stack-list-frames` command.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Wed, 5 Oct 2022 11:06:54 +0000 (14:06 +0300)
committerAlexander Soldatov/Platform Lab /SRR/Staff Engineer/Samsung Electronics <soldatov.a@samsung.com>
Thu, 6 Oct 2022 12:36:59 +0000 (15:36 +0300)
src/debugger/manageddebugger.cpp
src/debugger/manageddebugger.h
src/interfaces/idebugger.h
src/interfaces/types.h
src/protocols/miprotocol.cpp
src/protocols/protocol_utils.cpp
src/protocols/protocol_utils.h
test-suite/MITestHotReloadStepping/Program.cs

index 67f8345652ced194af473e02a61b299d4aec6a41..2365d6156d63c6767d02bfb8c67f901e27c05b97 100644 (file)
@@ -913,7 +913,7 @@ HRESULT ManagedDebugger::AllBreakpointsActivate(bool act)
     return m_uniqueBreakpoints->AllBreakpointsActivate(act);
 }
 
-static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModules, bool hotReload, ThreadId threadId, FrameLevel level, StackFrame &stackFrame)
+static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModules, bool hotReload, ThreadId threadId, FrameLevel level, StackFrame &stackFrame, bool hotReloadAwareCaller)
 {
     HRESULT Status;
 
@@ -923,11 +923,11 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
     ToRelease<ICorDebugModule> pModule;
     IfFailRet(pFunc->GetModule(&pModule));
 
+    ULONG32 methodVersion = 1;
+    ULONG32 currentVersion = 1;
     if (hotReload)
     {
         // In case current (top) code version is 1, executed in this frame method version can't be not 1.
-        ULONG32 currentVersion = 1;
-        ULONG32 methodVersion = 1;
         if (SUCCEEDED(pFunc->GetCurrentVersionNumber(&currentVersion)) && currentVersion != 1)
         {
             ToRelease<ICorDebugCode> pCode;
@@ -935,7 +935,7 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
             IfFailRet(pCode->GetVersionNumber(&methodVersion));
         }
 
-        if (methodVersion != currentVersion)
+        if (!hotReloadAwareCaller && methodVersion != currentVersion)
         {
             std::string moduleNamePrefix;
             WCHAR name[mdNameLen];
@@ -984,9 +984,17 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
     stackFrame.clrAddr.methodToken = methodToken;
     stackFrame.clrAddr.ilOffset = ilOffset;
     stackFrame.clrAddr.nativeOffset = nOffset;
+    stackFrame.clrAddr.methodVersion = methodVersion;
 
     stackFrame.addr = GetFrameAddr(pFrame);
 
+    if (stackFrame.clrAddr.ilOffset != 0)
+        stackFrame.activeStatementFlags |= StackFrame::ActiveStatementFlags::PartiallyExecuted;
+    if (methodVersion == currentVersion)
+        stackFrame.activeStatementFlags |= StackFrame::ActiveStatementFlags::MethodUpToDate;
+    else
+        stackFrame.activeStatementFlags |= StackFrame::ActiveStatementFlags::Stale;
+
     TypePrinter::GetMethodName(pFrame, stackFrame.name);
 
     return S_OK;
@@ -994,10 +1002,11 @@ static HRESULT InternalGetFrameLocation(ICorDebugFrame *pFrame, Modules *pModule
 
 HRESULT ManagedDebugger::GetFrameLocation(ICorDebugFrame *pFrame, ThreadId threadId, FrameLevel level, StackFrame &stackFrame)
 {
-    return InternalGetFrameLocation(pFrame, m_sharedModules.get(), m_hotReload, threadId, level, stackFrame);
+    return InternalGetFrameLocation(pFrame, m_sharedModules.get(), m_hotReload, threadId, level, stackFrame, false);
 }
 
-static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebugThread *pThread, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames)
+static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebugThread *pThread, FrameLevel startFrame,
+                                     unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
 {
     LogFuncEntry();
 
@@ -1009,6 +1018,14 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
 
     int currentFrame = -1;
 
+    auto AddFrameStatementFlag = [&] ()
+    {
+        if (currentFrame == 0)
+            stackFrames.back().activeStatementFlags |= StackFrame::ActiveStatementFlags::LeafFrame;
+        else
+            stackFrames.back().activeStatementFlags |= StackFrame::ActiveStatementFlags::NonLeafFrame;
+    };
+
     IfFailRet(WalkFrames(pThread, [&](
         FrameType frameType,
         ICorDebugFrame *pFrame,
@@ -1027,16 +1044,19 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
             case FrameUnknown:
                 stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, "?");
                 stackFrames.back().addr = GetFrameAddr(pFrame);
+                AddFrameStatementFlag();
                 break;
             case FrameNative:
                 stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, pNative->symbol);
                 stackFrames.back().addr = pNative->addr;
                 stackFrames.back().source = Source(pNative->file);
                 stackFrames.back().line = pNative->linenum;
+                AddFrameStatementFlag();
                 break;
             case FrameCLRNative:
                 stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, "[Native Frame]");
                 stackFrames.back().addr = GetFrameAddr(pFrame);
+                AddFrameStatementFlag();
                 break;
             case FrameCLRInternal:
                 {
@@ -1049,16 +1069,19 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
                     name += "]";
                     stackFrames.emplace_back(threadId, FrameLevel{currentFrame}, name);
                     stackFrames.back().addr = GetFrameAddr(pFrame);
+                    AddFrameStatementFlag();
                 }
                 break;
             case FrameCLRManaged:
                 {
                     StackFrame stackFrame;
-                    InternalGetFrameLocation(pFrame, pModules, hotReload, threadId, FrameLevel{currentFrame}, stackFrame);
+                    InternalGetFrameLocation(pFrame, pModules, hotReload, threadId, FrameLevel{currentFrame}, stackFrame, hotReloadAwareCaller);
                     stackFrames.push_back(stackFrame);
+                    AddFrameStatementFlag();
                 }
                 break;
         }
+
         return S_OK;
     }));
 
@@ -1067,7 +1090,7 @@ static HRESULT InternalGetStackTrace(Modules *pModules, bool hotReload, ICorDebu
     return S_OK;
 }
 
-HRESULT ManagedDebugger::GetStackTrace(ThreadId  threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames)
+HRESULT ManagedDebugger::GetStackTrace(ThreadId  threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller)
 {
     LogFuncEntry();
 
@@ -1077,7 +1100,7 @@ HRESULT ManagedDebugger::GetStackTrace(ThreadId  threadId, FrameLevel startFrame
 
     ToRelease<ICorDebugThread> pThread;
     IfFailRet(m_iCorProcess->GetThread(int(threadId), &pThread));
-    return InternalGetStackTrace(m_sharedModules.get(), m_hotReload, pThread, startFrame, maxFrames, stackFrames, totalFrames);
+    return InternalGetStackTrace(m_sharedModules.get(), m_hotReload, pThread, startFrame, maxFrames, stackFrames, totalFrames, hotReloadAwareCaller);
 }
 
 int ManagedDebugger::GetNamedVariables(uint32_t variablesReference)
index 4a1670b85a79c2d4b653af03bb3d22d8144f4bb8..74137fb967948457a94d4b3bf80bb881301b3f48 100644 (file)
@@ -152,7 +152,7 @@ public:
     HRESULT BreakpointActivate(int id, bool act) override;
     void EnumerateBreakpoints(std::function<bool (const IDebugger::BreakpointInfo&)>&& callback) override;
     HRESULT AllBreakpointsActivate(bool act) override;
-    HRESULT GetStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames) override;
+    HRESULT GetStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller = false) override;
     HRESULT StepCommand(ThreadId threadId, StepType stepType) override;
     HRESULT GetScopes(FrameId frameId, std::vector<Scope> &scopes) override;
     HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) override;
index b0499c8abcadc9b13a08040f4e7135ce16738902..79f50d1487d6af69fa42c04b8110be391fd7fc3a 100644 (file)
@@ -91,7 +91,7 @@ public:
     virtual HRESULT BreakpointActivate(int id, bool act) = 0;
     virtual void EnumerateBreakpoints(std::function<bool (const BreakpointInfo&)>&& callback) = 0;
     virtual HRESULT AllBreakpointsActivate(bool act) = 0;
-    virtual HRESULT GetStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames) = 0;
+    virtual HRESULT GetStackTrace(ThreadId threadId, FrameLevel startFrame, unsigned maxFrames, std::vector<StackFrame> &stackFrames, int &totalFrames, bool hotReloadAwareCaller = false) = 0;
     virtual HRESULT StepCommand(ThreadId threadId, StepType stepType) = 0;
     virtual HRESULT GetScopes(FrameId frameId, std::vector<Scope> &scopes) = 0;
     virtual HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) = 0;
index 051d648b0ec76d2d10f11de311f571b2ed1c9312..4c158d7d83c36e655b68c53f0d0ac6d023755385 100644 (file)
@@ -161,8 +161,10 @@ struct ClrAddr
     uint32_t ilOffset;
     uint32_t nativeOffset;
     uint32_t methodToken;
+    ULONG32 methodVersion; // EnC
 
-    ClrAddr() : ilOffset(0), nativeOffset(0), methodToken(0) {}
+    // Note, initial/default method code version is 1 (not zero!).
+    ClrAddr() : ilOffset(0), nativeOffset(0), methodToken(0), methodVersion(1) {}
     bool IsNull() const { return methodToken == 0; }
 };
 
@@ -187,18 +189,29 @@ public:
     ClrAddr clrAddr; // exposed for MI protocol
     uint64_t addr; // exposed for MI protocol
 
+    enum ActiveStatementFlags : uint16_t
+    {
+        None = 0x00,
+        LeafFrame = 0x01,
+        PartiallyExecuted = 0x02,
+        MethodUpToDate = 0x08,
+        NonLeafFrame = 0x10,
+        Stale = 0x20
+    };
+    uint16_t activeStatementFlags; // EnC
+
     StackFrame() :
         thread(ThreadId{}, true), level(FrameLevel{}, true), id(),
-        line(0), column(0), endLine(0), endColumn(0), addr(0) {}
+        line(0), column(0), endLine(0), endColumn(0), addr(0), activeStatementFlags(0) {}
 
     StackFrame(ThreadId threadId, FrameLevel level, const std::string& name) :
         thread(threadId, true), level(level, true), id(FrameId(threadId, level)),
-        name(name), line(0), column(0), endLine(0), endColumn(0), addr(0)
+        name(name), line(0), column(0), endLine(0), endColumn(0), addr(0), activeStatementFlags(0)
     {}
 
     StackFrame(FrameId id) :
         thread(ThreadId{}, false), level(FrameLevel{}, false), id(id),
-        line(0), column(0), endLine(0), endColumn(0), addr(0)
+        line(0), column(0), endLine(0), endColumn(0), addr(0), activeStatementFlags(0)
     {}
 
     FrameLevel GetLevel() const
index 003ba8d06fe56bb775a24df48a4dcc63982133b9..8e50a8aaeea18dede4697da223f5b4992a7c65f0 100644 (file)
@@ -125,6 +125,7 @@ static HRESULT PrintFrameLocation(const StackFrame &stackFrame, std::string &out
         ss << "clr-addr={module-id=\"{" << stackFrame.moduleId << "}\","
            << "method-token=\"0x"
            << std::setw(8) << std::setfill('0') << std::hex << stackFrame.clrAddr.methodToken << "\","
+           << "method-version=\"" << std::dec << stackFrame.clrAddr.methodVersion << "\","
            << "il-offset=\"" << std::dec << stackFrame.clrAddr.ilOffset
            << "\",native-offset=\"" << stackFrame.clrAddr.nativeOffset << "\"},";
     }
@@ -133,19 +134,50 @@ static HRESULT PrintFrameLocation(const StackFrame &stackFrame, std::string &out
     if (stackFrame.id)
         ss << ",addr=\"" << ProtocolUtils::AddrToString(stackFrame.addr) << "\"";
 
+    ss << ",active-statement-flags=\"";
+    if (stackFrame.activeStatementFlags == StackFrame::ActiveStatementFlags::None)
+    {
+        ss << "None";
+    }
+    else
+    {
+        struct flag_t
+        {
+            StackFrame::ActiveStatementFlags bit;
+            std::string name;
+            flag_t(StackFrame::ActiveStatementFlags bit_, const std::string &name_) : bit(bit_), name(name_) {}
+        };
+        static const std::vector<flag_t> flagsMap
+           {{StackFrame::ActiveStatementFlags::LeafFrame,          "LeafFrame"},
+            {StackFrame::ActiveStatementFlags::NonLeafFrame,       "NonLeafFrame"},
+            {StackFrame::ActiveStatementFlags::PartiallyExecuted,  "PartiallyExecuted"},
+            {StackFrame::ActiveStatementFlags::MethodUpToDate,     "MethodUpToDate"},
+            {StackFrame::ActiveStatementFlags::Stale,              "Stale"}};
+        bool first = true;
+        for (auto &flag : flagsMap)
+        {
+            if ((stackFrame.activeStatementFlags & flag.bit) == flag.bit)
+            {
+                ss << (first ? "":",") << flag.name;
+                first = false;
+            }
+        }
+    }
+    ss << "\"";
+
     output = ss.str();
 
     return stackFrame.source.IsNull() ? S_FALSE : S_OK;
 }
 
-static HRESULT PrintFrames(std::shared_ptr<IDebugger> &sharedDebugger, ThreadId threadId, std::string &output, FrameLevel lowFrame, FrameLevel highFrame)
+static HRESULT PrintFrames(std::shared_ptr<IDebugger> &sharedDebugger, ThreadId threadId, std::string &output, FrameLevel lowFrame, FrameLevel highFrame, bool hotReloadAwareCaller)
 {
     HRESULT Status;
     std::ostringstream ss;
 
     int totalFrames = 0;
     std::vector<StackFrame> stackFrames;
-    IfFailRet(sharedDebugger->GetStackTrace(threadId, lowFrame, int(highFrame) - int(lowFrame), stackFrames, totalFrames));
+    IfFailRet(sharedDebugger->GetStackTrace(threadId, lowFrame, int(highFrame) - int(lowFrame), stackFrames, totalFrames, hotReloadAwareCaller));
 
     int currentFrame = int(lowFrame);
 
@@ -698,11 +730,12 @@ static HRESULT HandleCommand(std::shared_ptr<IDebugger> &sharedDebugger, Breakpo
     { "stack-list-frames", [&](const std::vector<std::string> &args_orig, std::string &output) -> HRESULT {
         std::vector<std::string> args = args_orig;
         ThreadId threadId { ProtocolUtils::GetIntArg(args, "--thread", int(sharedDebugger->GetLastStoppedThreadId())) };
+        bool hotReloadAwareCaller = ProtocolUtils::FindAndEraseArg(args, "--hot-reload");
         int lowFrame = 0;
         int highFrame = FrameLevel::MaxFrameLevel;
         ProtocolUtils::StripArgs(args);
         ProtocolUtils::GetIndices(args, lowFrame, highFrame);
-        return PrintFrames(sharedDebugger, threadId, output, FrameLevel{lowFrame}, FrameLevel{highFrame});
+        return PrintFrames(sharedDebugger, threadId, output, FrameLevel{lowFrame}, FrameLevel{highFrame}, hotReloadAwareCaller);
     }},
     { "stack-list-variables", [&](const std::vector<std::string> &args, std::string &output) -> HRESULT {
         HRESULT Status;
index aee2f1cc06860b51b774efa9781212630a897cc8..e9a78d43bd062cdcf86c75a53417f0660ca303aa 100644 (file)
@@ -258,6 +258,23 @@ int GetIntArg(const std::vector<std::string> &args, const std::string& name, int
     return ok ? val : defaultValue;
 }
 
+// Return `true` in case arg was found and erased.
+bool FindAndEraseArg(std::vector<std::string> &args, const std::string& name)
+{
+    auto it = args.begin();
+    while (it != args.end())
+    {
+        if (*it == name)
+        {
+            it = args.erase(it);
+            return true;
+        }
+        else
+            ++it;
+    }
+    return false;
+}
+
 bool GetIndices(const std::vector<std::string> &args, int &index1, int &index2)
 {
     if (args.size() < 2)
index b6b6789492765a49677c59be782b24f70d53d8de..3b6460cbca380aba23a1f5e5e67e6732f6473ba0 100644 (file)
@@ -61,6 +61,7 @@ namespace ProtocolUtils
     int ParseInt(const std::string &s, bool &ok);
     void StripArgs(std::vector<std::string> &args);
     int GetIntArg(const std::vector<std::string> &args, const std::string& name, int defaultValue);
+    bool FindAndEraseArg(std::vector<std::string> &args, const std::string &name);
     bool GetIndices(const std::vector<std::string> &args, int &index1, int &index2);
     BreakType GetBreakpointType(const std::vector<std::string> &args);
     std::string GetConditionPrepareArgs(std::vector<std::string> &args);
index e4b2bc9f98357d47baa31d42adee279a3e1cf78f..bad9c303a229c32af24a1342a4dfb14b25979b59 100644 (file)
@@ -179,6 +179,42 @@ namespace NetcoreDbgTest.Script
             Assert.True(MIDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
+        public void CheckStackFrames(string caller_trace)\r
+        {\r
+            var res = MIDebugger.Request("-stack-list-frames 0 1000");\r
+                Assert.Equal(MIResultClass.Done, res.Class, @"__FILE__:__LINE__");\r
+\r
+            var stack = (MIList)res["stack"];\r
+            var frame = (MITuple)((MIResult)stack[0]).Value;\r
+            var level = ((MIConst)frame["level"]).CString;\r
+            var func = ((MIConst)frame["func"]).CString;\r
+\r
+            if (level == "0" && func.StartsWith("[Outdated Code]")) {\r
+                return;\r
+            }\r
+\r
+            throw new ResultNotSuccessException(@"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void CheckHotReloadAwareStackFrames(string caller_trace)\r
+        {\r
+            var res = MIDebugger.Request("-stack-list-frames 0 1000 --hot-reload");\r
+                Assert.Equal(MIResultClass.Done, res.Class, @"__FILE__:__LINE__");\r
+\r
+            var stack = (MIList)res["stack"];\r
+            var frame = (MITuple)((MIResult)stack[0]).Value;\r
+            var level = ((MIConst)frame["level"]).CString;\r
+            var func = ((MIConst)frame["func"]).CString;\r
+            var clr_addr = (MITuple)frame["clr-addr"];\r
+            var method_version = ((MIConst)clr_addr["method-version"]).CString;\r
+\r
+            if (level == "0" && func.StartsWith("TestAppHotReload") && method_version == "2") {\r
+                return;\r
+            }\r
+\r
+            throw new ResultNotSuccessException(@"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
         public void DebuggerExit(string caller_trace)\r
         {\r
             Assert.Equal(MIResultClass.Exit,\r
@@ -405,8 +441,13 @@ namespace MITestHotReloadStepping
 \r
                 Context.StepOver(@"__FILE__:__LINE__");\r
                 Context.WasStepInOutdatedCode(@"__FILE__:__LINE__");\r
+\r
+                Context.CheckStackFrames(@"__FILE__:__LINE__");\r
+                Context.CheckHotReloadAwareStackFrames(@"__FILE__:__LINE__");\r
+\r
                 Context.StepOver(@"__FILE__:__LINE__");\r
                 Context.WasStepInOutdatedCode(@"__FILE__:__LINE__");\r
+\r
                 Context.StepOver(@"__FILE__:__LINE__");\r
                 Context.WasStepInOutdatedCode(@"__FILE__:__LINE__");\r
 \r