From e7900e50e063c6a5820ae236366af8ef933fda4f Mon Sep 17 00:00:00 2001 From: Igor Kulaychuk Date: Tue, 16 Jan 2018 18:53:10 +0300 Subject: [PATCH] Refactor printing stack variables --- src/debug/netcoredbg/commands.cpp | 44 +++++- src/debug/netcoredbg/debugger.h | 57 +++++++- src/debug/netcoredbg/protocol.h | 52 +++++++ src/debug/netcoredbg/varobj.cpp | 299 +++++++++++++++++++++++++++++++++----- src/debug/netcoredbg/varobj.h | 1 - 5 files changed, 403 insertions(+), 50 deletions(-) diff --git a/src/debug/netcoredbg/commands.cpp b/src/debug/netcoredbg/commands.cpp index b86fcf7..00461c8 100644 --- a/src/debug/netcoredbg/commands.cpp +++ b/src/debug/netcoredbg/commands.cpp @@ -340,6 +340,32 @@ HRESULT MIProtocol::PrintFrames(int threadId, std::string &output, int lowFrame, return S_OK; } +HRESULT MIProtocol::PrintVariables(const std::vector &variables, std::string &output) +{ + const bool printValues = true; + const bool printTypes = false; + + HRESULT Status; + + std::stringstream ss; + ss << "variables=["; + const char *sep = ""; + + for (const Variable &var : variables) + { + ss << sep; + sep = ","; + + ss << "{name=\"" << EscapeMIValue(var.name) << "\""; + ss << ",value=\"" << EscapeMIValue(var.value) << "\""; + ss << "}"; + } + + ss << "]"; + output = ss.str(); + return S_OK; +} + void MIProtocol::EmitStoppedEvent(StoppedEvent event) { HRESULT Status; @@ -520,17 +546,19 @@ HRESULT MIProtocol::HandleCommand(std::string command, GetIndices(args, lowFrame, highFrame); return PrintFrames(threadId, output, lowFrame, highFrame); }}, - { "stack-list-variables", [](ICorDebugProcess *pProcess, const std::vector &args, std::string &output) -> HRESULT { - if (!pProcess) return E_FAIL; + { "stack-list-variables", [this](ICorDebugProcess *, const std::vector &args, std::string &output) -> HRESULT { HRESULT Status; - ToRelease pThread; - DWORD threadId = GetIntArg(args, "--thread", GetLastStoppedThreadId()); - IfFailRet(pProcess->GetThread(threadId, &pThread)); - ToRelease pFrame; - IfFailRet(GetFrameAt(pThread, GetIntArg(args, "--frame", 0), &pFrame)); + StackFrame stackFrame(GetIntArg(args, "--thread", GetLastStoppedThreadId()), GetIntArg(args, "--frame", 0), ""); + std::vector scopes; + std::vector variables; + IfFailRet(m_debugger->GetScopes(stackFrame.id, scopes)); + if (!scopes.empty() && scopes[0].variablesReference != 0) + { + IfFailRet(m_debugger->GetVariables(scopes[0].variablesReference, VariablesNamed, 0, 0, variables)); + } - IfFailRet(ListVariables(pThread, pFrame, output)); + PrintVariables(variables, output); return S_OK; }}, diff --git a/src/debug/netcoredbg/debugger.h b/src/debug/netcoredbg/debugger.h index 20b3275..44b8f8c 100644 --- a/src/debug/netcoredbg/debugger.h +++ b/src/debug/netcoredbg/debugger.h @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. #include "protocol.h" - +#include class ManagedCallback; class Protocol; @@ -35,6 +35,53 @@ private: DWORD m_processId; std::string m_clrPath; + struct VariableReference + { + uint32_t variablesReference; // key + int namedVariables; + int indexedVariables; + + std::string evaluateName; + + enum ValueKind + { + ValueIsScope, + ValueIsClass, + ValueIsVariable + }; + ValueKind valueKind; + ToRelease value; + uint64_t frameId; + + VariableReference(uint32_t variablesReference, uint64_t frameId, ToRelease value, ValueKind valueKind) : + variablesReference(variablesReference), + namedVariables(0), + indexedVariables(0), + valueKind(valueKind), + value(std::move(value)), + frameId(frameId) + {} + + VariableReference(uint32_t variablesReference, uint64_t frameId, int namedVariables) : + variablesReference(variablesReference), + namedVariables(namedVariables), + indexedVariables(0), + valueKind(ValueIsScope), + value(nullptr), + frameId(frameId) + {} + + bool IsScope() const { return valueKind == ValueIsScope; } + + VariableReference(VariableReference &&that) = default; + private: + VariableReference(const VariableReference &that) = delete; + }; + std::unordered_map m_variables; + uint32_t m_nextVariableReference; + + void AddVariableReference(Variable &variable, uint64_t frameId, ICorDebugValue *value, VariableReference::ValueKind valueKind); + HRESULT CheckNoProcess(); static VOID StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr); @@ -44,6 +91,8 @@ private: static HRESULT SetupStep(ICorDebugThread *pThread, StepType stepType); + HRESULT GetStackVariables(uint64_t frameId, ICorDebugThread *pThread, ICorDebugFrame *pFrame, int start, int count, std::vector &variables); + HRESULT GetChildren(VariableReference &ref, ICorDebugThread *pThread, ICorDebugFrame *pFrame, int start, int count, std::vector &variables); public: static bool IsJustMyCode() { return m_justMyCode; } static void SetJustMyCode(bool enable) { m_justMyCode = enable; } @@ -55,7 +104,8 @@ public: m_startupReady(false), m_startupResult(S_OK), m_unregisterToken(nullptr), - m_processId(0) {} + m_processId(0), + m_nextVariableReference(1) {} ~Debugger(); @@ -77,6 +127,8 @@ public: HRESULT SetBreakpoint(std::string filename, int linenum, Breakpoint &breakpoint); HRESULT GetStackTrace(int threadId, int lowFrame, int highFrame, std::vector &stackFrames); HRESULT StepCommand(int threadId, StepType stepType); + HRESULT GetScopes(uint64_t frameId, std::vector &scopes); + HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector &variables); }; class Protocol @@ -121,6 +173,7 @@ private: std::string &output, Debugger::StepType stepType); HRESULT PrintFrames(int threadId, std::string &output, int lowFrame, int highFrame); + HRESULT PrintVariables(const std::vector &variables, std::string &output); }; HRESULT DisableAllSteppers(ICorDebugProcess *pProcess); diff --git a/src/debug/netcoredbg/protocol.h b/src/debug/netcoredbg/protocol.h index 6ff8e47..653031e 100644 --- a/src/debug/netcoredbg/protocol.h +++ b/src/debug/netcoredbg/protocol.h @@ -7,6 +7,8 @@ #include "platform.h" #include +// From https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts + struct Thread { int id; @@ -59,6 +61,8 @@ struct StackFrame id |= level; } + StackFrame(uint64_t id) : id(id), line(0), column(0), endLine(0), endColumn(0) {} + uint32_t GetLevel() const { return id & 0xFFFFFFFFul; } int GetThreadId() const { return id >> 32; } }; @@ -151,3 +155,51 @@ struct OutputEvent OutputEvent(OutputCategory category, std::string output) : category(OutputConsole), output(output) {} }; + +struct Scope +{ + std::string name; + uint32_t variablesReference; + int namedVariables; + int indexedVariables; + bool expensive; + + Scope() : variablesReference(0), namedVariables(0), expensive(false) {} + + Scope(uint32_t variablesReference, const std::string &name, int namedVariables) : + name(name), + variablesReference(variablesReference), + namedVariables(namedVariables), + expensive(false) + {} +}; + + +// TODO: Replace strings with enums +struct VariablePresentationHint +{ + std::string kind; + std::vector attributes; + std::string visibility; +}; + +struct Variable +{ + std::string name; + std::string value; + std::string type; + VariablePresentationHint presentationHint; + std::string evaluateName; + uint32_t variablesReference; + int namedVariables; + int indexedVariables; + + Variable() : variablesReference(0), namedVariables(0), indexedVariables(0) {} +}; + +enum VariablesFilter +{ + VariablesNamed, + VariablesIndexed, + VariablesBoth +}; diff --git a/src/debug/netcoredbg/varobj.cpp b/src/debug/netcoredbg/varobj.cpp index f5ad27a..7feb217 100644 --- a/src/debug/netcoredbg/varobj.cpp +++ b/src/debug/netcoredbg/varobj.cpp @@ -21,6 +21,7 @@ #include "valueprint.h" #include "varobj.h" #include "expr.h" +#include "frames.h" HRESULT GetNumChild(ICorDebugValue *pValue, @@ -275,6 +276,85 @@ static HRESULT FetchFieldsAndProperties(ICorDebugValue *pInputValue, return S_OK; } +struct Member +{ + std::string name; + std::string ownerType; + ToRelease value; + Member(const std::string &name, const std::string ownerType, ToRelease value) : + name(name), + ownerType(ownerType), + value(std::move(value)) + {} + Member(Member &&that) = default; +private: + Member(const Member &that) = delete; +}; + +static HRESULT FetchFieldsAndProperties2(ICorDebugValue *pInputValue, + ICorDebugThread *pThread, + ICorDebugILFrame *pILFrame, + std::vector &members, + bool fetchOnlyStatic, + bool &hasStaticMembers, + int childStart, + int childEnd) +{ + hasStaticMembers = false; + HRESULT Status; + + DWORD threadId = 0; + IfFailRet(pThread->GetID(&threadId)); + + int currentIndex = -1; + + IfFailRet(WalkMembers(pInputValue, pThread, pILFrame, [&]( + mdMethodDef mdGetter, + ICorDebugModule *pModule, + ICorDebugType *pType, + ICorDebugValue *pValue, + bool is_static, + const std::string &name) + { + if (is_static) + hasStaticMembers = true; + + bool addMember = fetchOnlyStatic ? is_static : !is_static; + if (!addMember) + return S_OK; + + ++currentIndex; + if (currentIndex < childStart) + return S_OK; + if (currentIndex >= childEnd) + return S_OK; + + std::string className; + if (pType) + TypePrinter::GetTypeOfValue(pType, className); + + ToRelease pResultValue; + + if (mdGetter != mdMethodDefNil) + { + ToRelease pFunc; + if (SUCCEEDED(pModule->GetFunctionFromToken(mdGetter, &pFunc))) + EvalFunction(pThread, pFunc, pType, is_static ? nullptr : pInputValue, &pResultValue); + } + else + { + if (pValue) + pValue->AddRef(); + pResultValue = pValue; + } + + members.emplace_back(name, className, std::move(pResultValue)); + return S_OK; + })); + + return S_OK; +} + static void FixupInheritedFieldNames(std::vector &members) { std::unordered_set names; @@ -424,66 +504,207 @@ HRESULT ListChildren( return ListChildren(childStart, childEnd, it->second, print_values, pThread, pFrame, output); } -HRESULT ListVariables(ICorDebugThread *pThread, ICorDebugFrame *pFrame, std::string &output) +HRESULT Debugger::GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector &variables) { - const bool printValues = true; - const bool printTypes = false; + auto it = m_variables.find(variablesReference); + if (it == m_variables.end()) + return E_FAIL; + + VariableReference &ref = it->second; HRESULT Status; - std::stringstream ss; - ss << "variables=["; - const char *sep = ""; + StackFrame stackFrame(ref.frameId); + ToRelease pThread; + IfFailRet(m_pProcess->GetThread(stackFrame.GetThreadId(), &pThread)); + ToRelease pFrame; + IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame)); + + // Named and Indexed variables are in the same index (internally), Named variables go first + if (filter == VariablesNamed && (start + count > ref.namedVariables || count == 0)) + count = ref.namedVariables - start; + if (filter == VariablesIndexed) + start += ref.namedVariables; + + if (ref.IsScope()) + { + IfFailRet(GetStackVariables(ref.frameId, pThread, pFrame, start, count, variables)); + } else { + IfFailRet(GetChildren(ref, pThread, pFrame, start, count, variables)); + } + return S_OK; +} + +void Debugger::AddVariableReference(Variable &variable, uint64_t frameId, ICorDebugValue *value, VariableReference::ValueKind valueKind) +{ + HRESULT Status; + unsigned int numChild = 0; + GetNumChild(value, numChild, valueKind == VariableReference::ValueIsClass); + if (numChild == 0) + return; + + variable.namedVariables = numChild; + variable.variablesReference = m_nextVariableReference++; + value->AddRef(); + VariableReference variableReference(variable.variablesReference, frameId, value, valueKind); + variableReference.evaluateName = variable.evaluateName; + m_variables.emplace(std::make_pair(variable.variablesReference, std::move(variableReference))); +} + +HRESULT Debugger::GetStackVariables(uint64_t frameId, ICorDebugThread *pThread, ICorDebugFrame *pFrame, int start, int count, std::vector &variables) +{ + HRESULT Status; + + int currentIndex = -1; ToRelease pExceptionValue; if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr) { - ss << sep; - sep = ","; - - ss << "{name=\"" << "$exception" << "\""; - if (printValues) - { - std::string strVal; - if (SUCCEEDED(PrintValue(pExceptionValue, strVal))) - ss << ",value=\"" << strVal << "\""; - } - if (printTypes) + ++currentIndex; + bool outOfRange = currentIndex < start || (count != 0 && currentIndex >= start + count); + if (!outOfRange) { - std::string strVal; - if (SUCCEEDED(TypePrinter::GetTypeOfValue(pExceptionValue, strVal))) - ss << ",type=\"" << strVal << "\""; + Variable var; + var.name = "$exception"; + var.evaluateName = var.name; + bool escape = true; + PrintValue(pExceptionValue, var.value, escape); + TypePrinter::GetTypeOfValue(pExceptionValue, var.type); + AddVariableReference(var, frameId, pExceptionValue, VariableReference::ValueIsVariable); + variables.push_back(var); } - - ss << "}"; } IfFailRet(WalkStackVars(pFrame, [&](ICorDebugILFrame *pILFrame, ICorDebugValue *pValue, const std::string &name) -> HRESULT { - ss << sep; - sep = ","; + ++currentIndex; + if (currentIndex < start || (count != 0 && currentIndex >= start + count)) + return S_OK; + Variable var; + var.name = name; + var.evaluateName = var.name; + bool escape = true; + PrintValue(pValue, var.value, escape); + TypePrinter::GetTypeOfValue(pValue, var.type); + AddVariableReference(var, frameId, pValue, VariableReference::ValueIsVariable); + variables.push_back(var); + return S_OK; + })); - ss << "{name=\"" << name << "\""; - if (printValues) - { - std::string strVal; - if (SUCCEEDED(PrintValue(pValue, strVal))) - ss << ",value=\"" << strVal << "\""; - } - if (printTypes) + return S_OK; +} + +HRESULT Debugger::GetScopes(uint64_t frameId, std::vector &scopes) +{ + HRESULT Status; + + StackFrame stackFrame(frameId); + ToRelease pThread; + IfFailRet(m_pProcess->GetThread(stackFrame.GetThreadId(), &pThread)); + ToRelease pFrame; + IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame)); + + int namedVariables = 0; + uint32_t variablesReference = 0; + + ToRelease pExceptionValue; + if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr) + namedVariables++; + + IfFailRet(WalkStackVars(pFrame, [&](ICorDebugILFrame *pILFrame, ICorDebugValue *pValue, const std::string &name) -> HRESULT + { + namedVariables++; + return S_OK; + })); + + if (namedVariables > 0) + { + variablesReference = m_nextVariableReference++; + VariableReference scopeReference(variablesReference, frameId, namedVariables); + m_variables.emplace(std::make_pair(variablesReference, std::move(scopeReference))); + } + + scopes.emplace_back(variablesReference, "Locals", frameId); + + return S_OK; +} + +static void FixupInheritedFieldNames2(std::vector &members) +{ + std::unordered_set names; + for (auto &it : members) + { + auto r = names.insert(it.name); + if (!r.second) { - std::string strVal; - if (SUCCEEDED(TypePrinter::GetTypeOfValue(pValue, strVal))) - ss << ",type=\"" << strVal << "\""; + it.name += " (" + it.ownerType + ")"; } + } +} - ss << "}"; +HRESULT Debugger::GetChildren(VariableReference &ref, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + int start, + int count, + std::vector &variables) +{ + if (ref.IsScope()) + return E_INVALIDARG; + HRESULT Status; + + ToRelease pILFrame; + if (pFrame) + IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame)); + + std::vector members; + + bool hasStaticMembers = false; + + if (!ref.value) return S_OK; - })); - ss << "]"; - output = ss.str(); + IfFailRet(FetchFieldsAndProperties2(ref.value, + pThread, + pILFrame, + members, + ref.valueKind == VariableReference::ValueIsClass, + hasStaticMembers, + start, + count == 0 ? INT_MAX : start + count)); + + FixupInheritedFieldNames2(members); + + for (auto &it : members) + { + Variable var; + var.name = it.name; + bool isIndex = !it.name.empty() && it.name.at(0) == '['; + if (var.name.find('(') == std::string::npos) // expression evaluator does not support typecasts + var.evaluateName = ref.evaluateName + (isIndex ? "" : ".") + var.name; + bool escape = true; + PrintValue(it.value, var.value, escape); + TypePrinter::GetTypeOfValue(it.value, var.type); + AddVariableReference(var, ref.frameId, it.value, VariableReference::ValueIsVariable); + variables.push_back(var); + } + + if (ref.valueKind == VariableReference::ValueIsVariable && hasStaticMembers) + { + bool staticsInRange = start < ref.namedVariables && (count == 0 || start + count >= ref.namedVariables); + if (staticsInRange) + { + RunClassConstructor(pThread, ref.value); + + Variable var; + var.name = "Static members"; + TypePrinter::GetTypeOfValue(ref.value, var.evaluateName); // do not expose type for this fake variable + AddVariableReference(var, ref.frameId, ref.value, VariableReference::ValueIsClass); + variables.push_back(var); + } + } + return S_OK; } diff --git a/src/debug/netcoredbg/varobj.h b/src/debug/netcoredbg/varobj.h index 88f5a2a..4d0f5c0 100644 --- a/src/debug/netcoredbg/varobj.h +++ b/src/debug/netcoredbg/varobj.h @@ -2,7 +2,6 @@ // Distributed under the MIT License. // See the LICENSE file in the project root for more information. -HRESULT ListVariables(ICorDebugThread *pThread, ICorDebugFrame *pFrame, std::string &output); HRESULT CreateVar(ICorDebugThread *pThread, ICorDebugFrame *pFrame, const std::string &varobjName, const std::string &expression, std::string &output); HRESULT ListChildren(int childStart, int childEnd, const std::string &name, int print_values, ICorDebugThread *pThread, ICorDebugFrame *pFrame, std::string &output); HRESULT DeleteVar(const std::string &varobjName); -- 2.7.4