From: Alexander Aksenov Date: Wed, 8 Aug 2018 17:45:27 +0000 (+0300) Subject: Variables: add set value support and test X-Git-Tag: submit/tizen/20181014.073209~9 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=fe3d4e68f099cc5a7ab65222db010301b2a6aeb2;p=sdk%2Ftools%2Fnetcoredbg.git Variables: add set value support and test Signed-off-by: Alexander Aksenov --- diff --git a/src/debug/netcoredbg/CMakeLists.txt b/src/debug/netcoredbg/CMakeLists.txt index 2f815ab..aaee00f 100644 --- a/src/debug/netcoredbg/CMakeLists.txt +++ b/src/debug/netcoredbg/CMakeLists.txt @@ -41,7 +41,8 @@ set(netcoredbg_SRC frames.cpp jmc.cpp cputil.cpp - expr.cpp) + expr.cpp + valuewrite.cpp) set(CMAKE_INCLUDE_CURRENT_DIR ON) diff --git a/src/debug/netcoredbg/cputil.cpp b/src/debug/netcoredbg/cputil.cpp index 676506d..abaf24b 100644 --- a/src/debug/netcoredbg/cputil.cpp +++ b/src/debug/netcoredbg/cputil.cpp @@ -1,3 +1,7 @@ +// Copyright (c) 2018 Samsung Electronics Co., LTD +// Distributed under the MIT License. +// See the LICENSE file in the project root for more information. + #include "cputil.h" #include diff --git a/src/debug/netcoredbg/debugger.h b/src/debug/netcoredbg/debugger.h index 064e91a..24aa400 100644 --- a/src/debug/netcoredbg/debugger.h +++ b/src/debug/netcoredbg/debugger.h @@ -57,6 +57,8 @@ public: virtual HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector &variables) = 0; virtual int GetNamedVariables(uint32_t variablesReference) = 0; virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) = 0; + virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref) = 0; + virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value) = 0; }; class Protocol diff --git a/src/debug/netcoredbg/expr.cpp b/src/debug/netcoredbg/expr.cpp index a8579d0..668a28d 100644 --- a/src/debug/netcoredbg/expr.cpp +++ b/src/debug/netcoredbg/expr.cpp @@ -710,3 +710,16 @@ HRESULT Evaluator::EvalExpr(ICorDebugThread *pThread, return S_OK; } + +HRESULT Evaluator::CreateString(ICorDebugThread *pThread, const std::string &value, ICorDebugValue **ppNewString) +{ + HRESULT Status; + auto value16t = to_utf16(value); + + ToRelease pEval; + IfFailRet(pThread->CreateEval(&pEval)); + + IfFailRet(pEval->NewString(value16t.c_str())); + + return WaitEvalResult(pThread, pEval, ppNewString); +} diff --git a/src/debug/netcoredbg/manageddebugger.h b/src/debug/netcoredbg/manageddebugger.h index e1b0610..f664c08 100644 --- a/src/debug/netcoredbg/manageddebugger.h +++ b/src/debug/netcoredbg/manageddebugger.h @@ -164,6 +164,11 @@ public: HRESULT WalkStackVars(ICorDebugFrame *pFrame, WalkStackVarsCallback cb); + HRESULT CreateString( + ICorDebugThread *pThread, + const std::string &value, + ICorDebugValue **ppNewString); + void Cleanup(); }; @@ -312,6 +317,20 @@ class Variables unsigned int &numchild, bool static_members = false); + HRESULT SetStackVariable( + uint64_t frameId, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + const std::string &name, + const std::string &value); + + HRESULT SetChild( + VariableReference &ref, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + const std::string &name, + const std::string &value); + public: Variables(Evaluator &evaluator) : m_evaluator(evaluator), m_nextVariableReference(1) {} @@ -326,10 +345,28 @@ public: int count, std::vector &variables); + HRESULT SetVariable( + ICorDebugProcess *pProcess, + const std::string &name, + const std::string &value, + uint32_t ref); + + HRESULT SetVariable( + ICorDebugProcess *pProcess, + ICorDebugValue *pVariable, + const std::string &value, + uint64_t frameId); + HRESULT GetScopes(ICorDebugProcess *pProcess, uint64_t frameId, std::vector &scopes); HRESULT Evaluate(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, Variable &variable); + HRESULT GetValueByExpression( + ICorDebugProcess *pProcess, + uint64_t frameId, + const std::string &expression, + ICorDebugValue **ppResult); + void Clear() { m_variables.clear(); m_nextVariableReference = 1; } }; @@ -435,4 +472,6 @@ public: HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector &variables) override; int GetNamedVariables(uint32_t variablesReference) override; HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) override; + HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref) override; + HRESULT SetVariableByExpression(uint64_t frameId, const std::string &expression, const std::string &value) override; }; diff --git a/src/debug/netcoredbg/miprotocol.cpp b/src/debug/netcoredbg/miprotocol.cpp index 9a29033..52ca6c2 100644 --- a/src/debug/netcoredbg/miprotocol.cpp +++ b/src/debug/netcoredbg/miprotocol.cpp @@ -266,11 +266,34 @@ HRESULT MIProtocol::PrintVariables(const std::vector &variables, std:: return S_OK; } +bool MIProtocol::IsEditable(const std::string &type) +{ + if (type == "int" + || type == "bool" + || type == "char" + || type == "byte" + || type == "sbyte" + || type == "short" + || type == "ushort" + || type == "uint" + || type == "long" + || type == "ulong" + || type == "decimal" + || type == "string") + return true; + + return false; +} + void MIProtocol::PrintVar(const std::string &varobjName, Variable &v, int threadId, int print_values, std::string &output) { std::ostringstream ss; - std::string editable = "noneditable"; + std::string editable; + if (IsEditable(v.type)) + editable = "editable"; + else + editable = "noneditable"; ss << "name=\"" << varobjName << "\","; if (print_values) @@ -319,6 +342,17 @@ HRESULT MIProtocol::DeleteVar(const std::string &varobjName) return m_vars.erase(varobjName) == 0 ? E_FAIL : S_OK; } +HRESULT MIProtocol::FindVar(const std::string &varobjName, Variable &variable) +{ + auto it = m_vars.find(varobjName); + if (it == m_vars.end()) + return E_FAIL; + + variable = it->second; + + return S_OK; +} + void MIProtocol::Cleanup() { m_vars.clear(); @@ -796,8 +830,47 @@ HRESULT MIProtocol::HandleCommand(std::string command, return S_OK; }}, - { "var-show-attributes", [](const std::vector &args, std::string &output) -> HRESULT { - output = "status=\"noneditable\""; + { "var-show-attributes", [this](const std::vector &args, std::string &output) -> HRESULT { + HRESULT Status; + Variable variable; + std::string varName = args.at(0); + std::string editable; + + IfFailRet(FindVar(varName, variable)); + if (IsEditable(variable.type)) + editable = "editable"; + else + editable = "noneditable"; + + output = "status=\"" + editable + "\""; + return S_OK; + }}, + { "var-assign", [this](const std::vector &args, std::string &output) -> HRESULT { + HRESULT Status; + + if (args.size() < 2) + { + output = "Command requires at least 2 arguments"; + return E_FAIL; + } + + std::string varName = args.at(0); + std::string varExpr = args.at(1); + + if (varExpr.size() >= 2 && varExpr.front() == '"' && varExpr.back() == '"') + varExpr = varExpr.substr(1, varExpr.size() - 2); + + int threadId = GetIntArg(args, "--thread", m_debugger->GetLastStoppedThreadId()); + int level = GetIntArg(args, "--frame", 0); + uint64_t frameId = StackFrame(threadId, level, "").id; + + Variable variable; + IfFailRet(FindVar(varName, variable)); + + m_debugger->SetVariableByExpression(frameId, variable.evaluateName, varExpr); + + output = "value=\"" + MIProtocol::EscapeMIValue(varExpr) + "\""; + return S_OK; }}, }; @@ -813,51 +886,75 @@ HRESULT MIProtocol::HandleCommand(std::string command, return command_it->second(args, output); } -static std::vector TokenizeString(const std::string &str, const char *delimiters = " \t\n\r") +class Tokenizer { - enum { - StateSpace, - StateToken, - StateQuotedToken, - StateEscape - } state = StateSpace; + std::string m_str; + std::string m_delimiters; + size_t m_next; +public: - std::vector result; + Tokenizer(const std::string &str, const std::string &delimiters = " \t\n\r") + : m_str(str), m_delimiters(delimiters), m_next(0) + { + m_str.erase(m_str.find_last_not_of(m_delimiters) + 1); + } - for (char c : str) + bool Next(std::string &token) { - switch(state) + token = ""; + + if (m_next >= m_str.size()) + return false; + + enum { + StateSpace, + StateToken, + StateQuotedToken, + StateEscape + } state = StateSpace; + + for (; m_next < m_str.size(); m_next++) { - case StateSpace: - if (strchr(delimiters, c)) - continue; - result.emplace_back(); - state = c == '"' ? StateQuotedToken : StateToken; - if (state != StateQuotedToken) - result.back() +=c; - break; - case StateToken: - if (strchr(delimiters, c)) - state = StateSpace; - else - result.back() += c; - break; - case StateQuotedToken: - if (c == '\\') - state = StateEscape; - else if (c == '"') - state = StateSpace; - else - result.back() += c; - break; - case StateEscape: - result.back() += c; - state = StateQuotedToken; - break; + char c = m_str.at(m_next); + switch(state) + { + case StateSpace: + if (m_delimiters.find(c) != std::string::npos) + continue; + if (!token.empty()) + return true; + state = c == '"' ? StateQuotedToken : StateToken; + if (state != StateQuotedToken) + token +=c; + break; + case StateToken: + if (m_delimiters.find(c) != std::string::npos) + state = StateSpace; + else + token += c; + break; + case StateQuotedToken: + if (c == '\\') + state = StateEscape; + else if (c == '"') + state = StateSpace; + else + token += c; + break; + case StateEscape: + token += c; + state = StateQuotedToken; + break; + } } + return state != StateEscape || token.empty(); } - return result; -} + + std::string Remain() const + { + return m_str.substr(m_next); + } +}; static bool ParseLine(const std::string &str, std::string &token, @@ -868,24 +965,32 @@ static bool ParseLine(const std::string &str, cmd.clear(); args.clear(); - std::vector result = TokenizeString(str); + Tokenizer tokenizer(str); + std::string result; - if (result.empty()) + if (!tokenizer.Next(result) || result.empty()) return false; - auto cmd_it = result.begin(); - - std::size_t i = cmd_it->find_first_not_of("0123456789"); + std::size_t i = result.find_first_not_of("0123456789"); if (i == std::string::npos) return false; - if (cmd_it->at(i) != '-') + if (result.at(i) != '-') return false; - token = cmd_it->substr(0, i); - cmd = cmd_it->substr(i + 1); - result.erase(result.begin()); - args = result; + token = result.substr(0, i); + cmd = result.substr(i + 1); + + if (cmd == "var-assign") + { + tokenizer.Next(result); + args.push_back(result); + args.push_back(tokenizer.Remain()); + return true; + } + + while (tokenizer.Next(result)) + args.push_back(result); return true; } diff --git a/src/debug/netcoredbg/miprotocol.h b/src/debug/netcoredbg/miprotocol.h index 17ace81..684a240 100644 --- a/src/debug/netcoredbg/miprotocol.h +++ b/src/debug/netcoredbg/miprotocol.h @@ -32,6 +32,8 @@ class MIProtocol : public Protocol static void Printf(const char *fmt, ...) __attribute__((format (printf, 1, 2))); #endif + static bool IsEditable(const std::string &type); + public: MIProtocol() : Protocol(), m_varCounter(0) {} @@ -65,6 +67,7 @@ private: HRESULT PrintVariables(const std::vector &variables, std::string &output); HRESULT CreateVar(int threadId, int level, const std::string &varobjName, const std::string &expression, std::string &output); HRESULT DeleteVar(const std::string &varobjName); + HRESULT FindVar(const std::string &varobjName, Variable &variable); void PrintChildren(std::vector &children, int threadId, int print_values, bool has_more, std::string &output); void PrintNewVar(std::string varobjName, Variable &v, int threadId, int print_values, std::string &output); HRESULT ListChildren(int threadId, int level, int childStart, int childEnd, const std::string &varName, int print_values, std::string &output); diff --git a/src/debug/netcoredbg/symbolreader.h b/src/debug/netcoredbg/symbolreader.h index fdf4b0d..58ba424 100644 --- a/src/debug/netcoredbg/symbolreader.h +++ b/src/debug/netcoredbg/symbolreader.h @@ -66,19 +66,13 @@ private: public: static const int HiddenLine; -#ifdef _MSC_VER -#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) ) -#else -#define PACK( __Declaration__ ) __Declaration__ __attribute__((packed)) -#endif - - PACK(struct SequencePoint { + PACK_BEGIN struct SequencePoint { int32_t startLine; int32_t startColumn; int32_t endLine; int32_t endColumn; int32_t offset; - }); + } PACK_END; SymbolReader() { diff --git a/src/debug/netcoredbg/torelease.h b/src/debug/netcoredbg/torelease.h index e944ad2..fa49d1e 100644 --- a/src/debug/netcoredbg/torelease.h +++ b/src/debug/netcoredbg/torelease.h @@ -118,3 +118,11 @@ typedef ULONG64 CLRDATA_ADDRESS; #define TO_CDADDR(taddr) ((CLRDATA_ADDRESS)(LONG_PTR)(taddr)) const int mdNameLen = 2048; + +#ifdef _MSC_VER +#define PACK_BEGIN __pragma( pack(push, 1) ) +#define PACK_END __pragma( pack(pop) ) +#else +#define PACK_BEGIN +#define PACK_END __attribute__((packed)) +#endif diff --git a/src/debug/netcoredbg/valuewrite.cpp b/src/debug/netcoredbg/valuewrite.cpp new file mode 100644 index 0000000..5a11e55 --- /dev/null +++ b/src/debug/netcoredbg/valuewrite.cpp @@ -0,0 +1,343 @@ +// Copyright (c) 2018 Samsung Electronics Co., LTD +// Distributed under the MIT License. +// See the LICENSE file in the project root for more information. + +#ifndef FEATURE_PAL +// Turn off macro definitions named max and min in header file +// to avoid compile error for std::numeric_limits::max(). +#define NOMINMAX +#endif + +#include "valuewrite.h" + +#include +#include +#include +#include +#include + +#include + +#include "torelease.h" +#include "cputil.h" +#include "typeprinter.h" + + +PACK_BEGIN struct Decimal { + unsigned int unused : 16; + unsigned int exponent : 8; + unsigned int unused2 : 7; + unsigned int sign : 1; + + uint32_t hi; + uint32_t lo; + uint32_t mid; + + Decimal() : unused(0), exponent(0), unused2(0), sign(0), hi(0), lo(0), mid(0) {} + Decimal(uint32_t lo, uint32_t mid, uint32_t hi, uint8_t exp, unsigned int sign) : unused(0), + exponent(exp), unused2(0), sign(sign), hi(hi), lo(lo), mid(mid) {} + void Mul10(); + void AddInt32(uint32_t val); + bool Parse(const std::string &value); + +private: + void ShiftLeft(); + void Add(Decimal &d); + static bool AddCarry(uint32_t &to, uint32_t val); +} PACK_END; + +bool Decimal::AddCarry(uint32_t &to, uint32_t val) +{ + uint32_t v = to; + to += val; + return (to < v) || (to < val); +} + +void Decimal::ShiftLeft() +{ + uint32_t c0 = (lo & 0x80000000) != 0 ? 1u : 0u; + uint32_t c1 = (mid & 0x80000000) != 0 ? 1u : 0u; + + lo = lo << 1; + mid = (mid << 1) | c0; + hi = (hi << 1) | c1; +} + +void Decimal::Add(Decimal &d) +{ + if (AddCarry(lo, d.lo)) + if (AddCarry(mid, 1)) + AddCarry(hi, 1); + + if (AddCarry(mid, d.mid)) + AddCarry(hi, 1); + + AddCarry(hi, d.hi); +} + +void Decimal::Mul10() +{ + Decimal d(lo, mid, hi, exponent, sign); + + ShiftLeft(); + ShiftLeft(); + Add(d); + ShiftLeft(); +} + +void Decimal::AddInt32(uint32_t val) +{ + if (AddCarry(lo, val)) + if (AddCarry(mid, 1)) + AddCarry(hi, 1); +} + + + + +const int DECIMAL_PRECISION = 29; + +bool Decimal::Parse(const std::string &value) +{ + std::string valueToStore(value); + bool is_negative = valueToStore.length() > 0 && valueToStore[0] == '-'; + + if (is_negative) { + sign = 1; + valueToStore.erase(0, 1); + } + else + { + sign = 0; + } + + // Cut all meaningless first zeroes + const auto ix = valueToStore.find_first_not_of('0'); + valueToStore.erase(0, ix); + + const auto dotPos = valueToStore.find("."); + if (dotPos != std::string::npos) + valueToStore.erase(dotPos, 1); + + if (dotPos > std::numeric_limits::max()) + return false; + + int scale = static_cast(dotPos); + + const unsigned char* p = reinterpret_cast(valueToStore.c_str()); + + if (*p == 0) + { + // To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force + // the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.) + if (scale > 0) + scale = 0; + } + else + { + if (scale > DECIMAL_PRECISION) + return false; + + while (((scale > 0) || ((*p != 0) && (scale > -(DECIMAL_PRECISION - 1)))) && + ((hi < 0x19999999) || ((hi == 0x19999999) && + ((mid < 0x99999999) || ((mid == 0x99999999) && + ((lo < 0x99999999) || ((lo == 0x99999999) && + (*p <= '5')))))))) + { + Mul10(); + + if (*p != 0) + AddInt32((uint32_t)(*p++ - '0')); + scale--; + } + + if (*p++ >= '5') + { + bool round = true; + + if ((*(p - 1) == '5') && ((*(p - 2) % 2) == 0)) + { + // Check if previous digit is even, only if the when we are unsure whether hows to do + // Banker's rounding. For digits > 5 we will be rounding up anyway. + int count = 20; // Look at the next 20 digits to check to round + while ((*p == '0') && (count != 0)) + { + p++; + count--; + } + if ((*p == '\0') || (count == 0)) + round = false;// Do nothing + } + + if (round) + { + AddInt32(1); + if ((hi | mid | lo) == 0) + { + // If we got here, the magnitude portion overflowed and wrapped back to 0 as the magnitude was already at the MaxValue point: + // + // 79,228,162,514,264,337,593,543,950,335e+X + // + // Manually force it to the correct result: + // + // 7,922,816,251,426,433,759,354,395,034e+(X+1) + // + // This code path can be reached by trying to parse the following as a Decimal: + // + // 0.792281625142643375935439503355e28 + // + + hi = 0x19999999; + mid = 0x99999999; + lo = 0x9999999A; + scale++; + } + } + } + } + + if (scale > 0) + return false; // Rounding may have caused its own overflow. For example, parsing "0.792281625142643375935439503355e29" will get here. + + if (scale <= -DECIMAL_PRECISION) + { + // Parsing a large scale zero can give you more precision than fits in the decimal. + // This should only happen for actual zeros or very small numbers that round to zero. + hi = 0; + lo = 0; + mid = 0; + exponent = DECIMAL_PRECISION - 1; + } + else + { + exponent = -scale; + } + + return true; +} + +HRESULT WriteValue(ICorDebugValue *pValue, const std::string &value, ICorDebugThread *pThread, Evaluator &evaluator) +{ + HRESULT Status; + + ULONG32 size; + IfFailRet(pValue->GetSize(&size)); + + ArrayHolder valBuf = new (std::nothrow) BYTE[size]; + if (valBuf == nullptr) + return E_OUTOFMEMORY; + + CorElementType corType; + IfFailRet(pValue->GetType(&corType)); + + if (corType == ELEMENT_TYPE_STRING) + { + ToRelease pNewString; + IfFailRet(evaluator.CreateString(pThread, value, &pNewString)); + + // Switch object addresses + ToRelease pRefNew; + IfFailRet(pNewString->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID *) &pRefNew)); + ToRelease pRefOld; + IfFailRet(pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID *) &pRefOld)); + + CORDB_ADDRESS addr; + IfFailRet(pRefNew->GetValue(&addr)); + IfFailRet(pRefOld->SetValue(addr)); + + return S_OK; + } + + ToRelease pGenValue; + IfFailRet(pValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID *) &pGenValue)); + + std::stringstream ss(value); + + switch (corType) + { + case ELEMENT_TYPE_BOOLEAN: + if (value == "false") + valBuf[0] = 0; + else if (value == "true") + valBuf[0] = 1; + else + return E_INVALIDARG; + + break; + case ELEMENT_TYPE_CHAR: + { + auto wval = to_utf16(value); + + if (wval.length() > 1) + return E_INVALIDARG; + + std::memcpy(&valBuf[0], &wval[0], sizeof(wval[0])); + + break; + } + case ELEMENT_TYPE_I1: + { + int num; + + ss >> num; + valBuf[0] = (char)num; + break; + } + case ELEMENT_TYPE_U1: + { + int num; + + ss >> num; + valBuf[0] = (unsigned char)num; + break; + } + case ELEMENT_TYPE_I2: + ss >> *(short *)&valBuf[0]; + break; + case ELEMENT_TYPE_U2: + ss >> *(unsigned short *)&valBuf[0]; + break; + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_I: + ss >> *(int *)&valBuf[0]; + break; + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_U: + ss >> *(unsigned int *)&valBuf[0]; + break; + case ELEMENT_TYPE_I8: + ss >> *(__int64 *)&valBuf[0]; + break; + case ELEMENT_TYPE_U8: + ss >> *(unsigned __int64 *)&valBuf[0]; + break; + case ELEMENT_TYPE_R4: + ss >> std::setprecision(8) >> *(float *)&valBuf[0]; + break; + case ELEMENT_TYPE_R8: + ss >> std::setprecision(16) >> *(double *)&valBuf[0]; + break; + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + { + std::string typeName; + TypePrinter::GetTypeOfValue(pValue, typeName); + if (typeName == "decimal") + { + Decimal d; + if (!d.Parse(value)) + return E_INVALIDARG; + if (sizeof(Decimal) > size) + return E_FAIL; + std::memcpy(&valBuf[0], &d, size); + } + break; + } + default: + return S_OK; + } + + IfFailRet(pGenValue->SetValue((LPVOID) &valBuf[0])); + + return S_OK; +} diff --git a/src/debug/netcoredbg/valuewrite.h b/src/debug/netcoredbg/valuewrite.h new file mode 100644 index 0000000..4ea2235 --- /dev/null +++ b/src/debug/netcoredbg/valuewrite.h @@ -0,0 +1,13 @@ +// Copyright (c) 2018 Samsung Electronics Co., LTD +// Distributed under the MIT License. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include + +#include +#include +#include "manageddebugger.h" + +HRESULT WriteValue(ICorDebugValue *pValue, const std::string &value, ICorDebugThread *pThread, Evaluator &evaluator); diff --git a/src/debug/netcoredbg/variables.cpp b/src/debug/netcoredbg/variables.cpp index 9efce4a..0ce8567 100644 --- a/src/debug/netcoredbg/variables.cpp +++ b/src/debug/netcoredbg/variables.cpp @@ -6,9 +6,13 @@ #include #include +#include + +#include #include "typeprinter.h" #include "valueprint.h" +#include "valuewrite.h" #include "frames.h" @@ -427,3 +431,150 @@ HRESULT Variables::Evaluate( return S_OK; } + +HRESULT ManagedDebugger::SetVariable(const std::string &name, const std::string &value, uint32_t ref) +{ + return m_variables.SetVariable(m_pProcess, name, value, ref); +} + +HRESULT Variables::SetVariable( + ICorDebugProcess *pProcess, + const std::string &name, + const std::string &value, + uint32_t ref) +{ + if (pProcess == nullptr) + return E_FAIL; + + auto it = m_variables.find(ref); + if (it == m_variables.end()) + return E_FAIL; + + VariableReference &varRef = it->second; + HRESULT Status; + + StackFrame stackFrame(varRef.frameId); + ToRelease pThread; + IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread)); + ToRelease pFrame; + IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame)); + + if (varRef.IsScope()) + { + IfFailRet(SetStackVariable(varRef.frameId, pThread, pFrame, name, value)); + } + else + { + IfFailRet(SetChild(varRef, pThread, pFrame, name, value)); + } + + return S_OK; +} + +HRESULT Variables::SetStackVariable( + uint64_t frameId, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + const std::string &name, + const std::string &value) +{ + HRESULT Status; + + // TODO Exception? + + IfFailRet(m_evaluator.WalkStackVars(pFrame, [&]( + ICorDebugILFrame *pILFrame, + ICorDebugValue *pValue, + const std::string &varName) -> HRESULT + { + if (varName == name) + IfFailRet(WriteValue(pValue, value, pThread, m_evaluator)); + + return S_OK; + })); + + return S_OK; +} + +HRESULT Variables::SetChild( + VariableReference &ref, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + const std::string &name, + const std::string &value) +{ + if (ref.IsScope()) + return E_INVALIDARG; + + if (!ref.value) + return S_OK; + + HRESULT Status; + + ToRelease pILFrame; + if (pFrame) + IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame)); + + IfFailRet(m_evaluator.WalkMembers(ref.value, pThread, pILFrame, [&]( + mdMethodDef mdGetter, + ICorDebugModule *pModule, + ICorDebugType *pType, + ICorDebugValue *pValue, + bool is_static, + const std::string &varName) -> HRESULT + { + if (varName == name) + IfFailRet(WriteValue(pValue, value, pThread, m_evaluator)); + + return S_OK; + })); + + return S_OK; +} + +HRESULT ManagedDebugger::SetVariableByExpression( + uint64_t frameId, + const std::string &expression, + const std::string &value) +{ + HRESULT Status; + ToRelease pResultValue; + + IfFailRet(m_variables.GetValueByExpression(m_pProcess, frameId, expression, &pResultValue)); + return m_variables.SetVariable(m_pProcess, pResultValue, value, frameId); +} + +HRESULT Variables::GetValueByExpression(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, + ICorDebugValue **ppResult) +{ + if (pProcess == nullptr) + return E_FAIL; + + HRESULT Status; + + StackFrame stackFrame(frameId); + ToRelease pThread; + IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread)); + ToRelease pFrame; + IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame)); + + return m_evaluator.EvalExpr(pThread, pFrame, expression, ppResult); +} + +HRESULT Variables::SetVariable( + ICorDebugProcess *pProcess, + ICorDebugValue *pVariable, + const std::string &value, + uint64_t frameId) +{ + HRESULT Status; + + if (pProcess == nullptr) + return E_FAIL; + + StackFrame stackFrame(frameId); + ToRelease pThread; + IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread)); + + return WriteValue(pVariable, value, pThread, m_evaluator); +} diff --git a/src/debug/netcoredbg/vscodeprotocol.cpp b/src/debug/netcoredbg/vscodeprotocol.cpp index b15835b..b437059 100644 --- a/src/debug/netcoredbg/vscodeprotocol.cpp +++ b/src/debug/netcoredbg/vscodeprotocol.cpp @@ -409,6 +409,18 @@ HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &ar return E_INVALIDARG; return m_debugger->Attach(processId); + } }, + { "setVariable", [this](const json &arguments, json &body) { + HRESULT Status; + + std::string name = arguments.at("name"); + std::string value = arguments.at("value"); + int ref = arguments.at("variablesReference"); + + IfFailRet(m_debugger->SetVariable(name, value, ref)); + body["value"] = value; + + return S_OK; } } }; diff --git a/tests/SetValuesTest/SetValuesTest.cs b/tests/SetValuesTest/SetValuesTest.cs new file mode 100644 index 0000000..022c629 --- /dev/null +++ b/tests/SetValuesTest/SetValuesTest.cs @@ -0,0 +1,228 @@ +/* +Send("1-file-exec-and-symbols dotnet"); +Send("2-exec-arguments " + TestBin); +Send("3-exec-run"); + +var r = Expect("*stopped"); +Assert.Equal("entry-point-hit", r.FindString("reason")); + +Send(String.Format("4-break-insert -f {0}:{1}", TestSource, Lines["BREAK1"])); +Expect("4^done"); + +Send(String.Format("5-break-insert -f {0}:{1}", TestSource, Lines["BREAK2"])); +Expect("5^done"); + +Send("6-exec-continue"); +*/ + +using System; + +namespace SetValuesTest +{ + public struct TestStruct1 + { + public int val1; + public byte val2; + + public TestStruct1(int v1, byte v2) + { + val1 = v1; + val2 = v2; + } + } + + public struct TestStruct2 + { + public int val1; + public TestStruct1 struct2; + + public TestStruct2(int v1, int v2, byte v3) + { + val1 = v1; + struct2.val1 = v2; + struct2.val2 = v3; + } + } + + class Program + { + static void Main(string[] args) + { // //@START@ + TestStruct2 ts = new TestStruct2(1, 5, 10); + + bool testBool = false; + char testChar = 'ㅎ'; + byte testByte = (byte)10; + sbyte testSByte = (sbyte)-100; + short testShort = (short)-500; + ushort testUShort = (ushort)500; + int testInt = -999999; + uint testUInt = 999999; + long testLong = -999999999; + ulong testULong = 9999999999; + + decimal b = 0000001.000000000000000000000000006M; + int[] arrs = decimal.GetBits(b); + string testString = "someNewString that I'll test with"; + + int dummy1 = 1; // //@BREAK1@ +/* +r = Expect("*stopped"); +Assert.Equal("breakpoint-hit", r.FindString("reason")); +Assert.Equal(Lines["BREAK1"], r.Find("frame").FindInt("line")); + +Send(String.Format("8-var-create - * \"{0}\"", "ts.struct2.val1")); +r = Expect("8^done"); +string val1 = r.FindString("name"); +Send(String.Format("9-var-assign {0} \"666\"", val1)); +r = Expect("9^done"); + +Send(String.Format("10-var-create - * \"{0}\"", "testBool")); +r = Expect("10^done"); +string testBool = r.FindString("name"); +Send(String.Format("11-var-assign {0} \"true\"", testBool)); +r = Expect("11^done"); + +Send(String.Format("12-var-create - * \"{0}\"", "testChar")); +r = Expect("12^done"); +string testChar = r.FindString("name"); +Send(String.Format("13-var-assign {0} \"a\"", testChar)); +r = Expect("13^done"); + +Send(String.Format("14-var-create - * \"{0}\"", "testByte")); +r = Expect("14^done"); +string testByte = r.FindString("name"); +Send(String.Format("15-var-assign {0} \"200\"", testByte)); +r = Expect("15^done"); + +Send(String.Format("16-var-create - * \"{0}\"", "testSByte")); +r = Expect("16^done"); +string testSByte = r.FindString("name"); +Send(String.Format("17-var-assign {0} \"-1\"", testSByte)); +r = Expect("17^done"); + +Send(String.Format("18-var-create - * \"{0}\"", "testShort")); +r = Expect("18^done"); +string testShort = r.FindString("name"); +Send(String.Format("19-var-assign {0} \"-666\"", testShort)); +r = Expect("19^done"); + +Send(String.Format("20-var-create - * \"{0}\"", "testUShort")); +r = Expect("20^done"); +string testUShort = r.FindString("name"); +Send(String.Format("21-var-assign {0} \"666\"", testUShort)); +r = Expect("21^done"); + +Send(String.Format("22-var-create - * \"{0}\"", "testInt")); +r = Expect("22^done"); +string testInt = r.FindString("name"); +Send(String.Format("23-var-assign {0} \"666666\"", testInt)); +r = Expect("23^done"); + +Send(String.Format("24-var-create - * \"{0}\"", "testUInt")); +r = Expect("24^done"); +string testUInt = r.FindString("name"); +Send(String.Format("25-var-assign {0} \"666666\"", testUInt)); +r = Expect("25^done"); + +Send(String.Format("26-var-create - * \"{0}\"", "testLong")); +r = Expect("26^done"); +string testLong = r.FindString("name"); +Send(String.Format("27-var-assign {0} \"-666666666\"", testLong)); +r = Expect("27^done"); + +Send(String.Format("28-var-create - * \"{0}\"", "testULong")); +r = Expect("28^done"); +string testULong = r.FindString("name"); +Send(String.Format("29-var-assign {0} \"666666666\"", testULong)); +r = Expect("29^done"); + +Send(String.Format("30-var-create - * \"{0}\"", "b")); +r = Expect("30^done"); +string b = r.FindString("name"); +Send(String.Format("31-var-assign {0} \"-1.000000000000000000000017\"", b)); +r = Expect("31^done"); + +Send(String.Format("32-var-create - * \"{0}\"", "testString")); +r = Expect("32^done"); +string testString = r.FindString("name"); +Send(String.Format("33-var-assign {0} \"edited string\"", testString)); +r = Expect("33^done"); +Send("34-exec-continue"); +*/ + int dummy2 = 2; // //@BREAK2@ +/* +r = Expect("*stopped"); +Assert.Equal("breakpoint-hit", r.FindString("reason")); +Assert.Equal(Lines["BREAK2"], r.Find("frame").FindInt("line")); + +Send(String.Format("35-var-create - * \"{0}\"", "ts.struct2")); +r = Expect("35^done"); +string struct2 = r.FindString("name"); +Send(String.Format("36-var-list-children --simple-values \"{0}\"", struct2)); +r = Expect("36^done"); +string val = r.Find("children").Find("child").FindString("value"); +Assert.Equal("666", val); + + + + + + +Send(String.Format("37-var-create - * \"{0}\"", "testBool")); +r = Expect("37^done"); +Assert.Equal(r.FindString("value"), "true"); + +Send(String.Format("38-var-create - * \"{0}\"", "testChar")); +r = Expect("38^done"); +Assert.Equal(r.FindString("value"), "97 \'a\'"); + +Send(String.Format("39-var-create - * \"{0}\"", "testByte")); +r = Expect("39^done"); +Assert.Equal(r.FindString("value"), "200"); + +Send(String.Format("40-var-create - * \"{0}\"", "testSByte")); +r = Expect("40^done"); +Assert.Equal(r.FindString("value"), "-1"); + +Send(String.Format("41-var-create - * \"{0}\"", "testShort")); +r = Expect("41^done"); +Assert.Equal(r.FindString("value"), "-666"); + +Send(String.Format("42-var-create - * \"{0}\"", "testUShort")); +r = Expect("42^done"); +Assert.Equal(r.FindString("value"), "666"); + +Send(String.Format("43-var-create - * \"{0}\"", "testInt")); +r = Expect("43^done"); +Assert.Equal(r.FindString("value"), "666666"); + +Send(String.Format("44-var-create - * \"{0}\"", "testUInt")); +r = Expect("44^done"); +Assert.Equal(r.FindString("value"), "666666"); + +Send(String.Format("45-var-create - * \"{0}\"", "testLong")); +r = Expect("45^done"); +Assert.Equal(r.FindString("value"), "-666666666"); + +Send(String.Format("46-var-create - * \"{0}\"", "testULong")); +r = Expect("46^done"); +Assert.Equal(r.FindString("value"), "666666666"); + +Send(String.Format("47-var-create - * \"{0}\"", "b")); +r = Expect("47^done"); +Assert.Equal(r.FindString("value"), "-1.000000000000000000000017"); + +Send(String.Format("48-var-create - * \"{0}\"", "testString")); +r = Expect("48^done"); +Assert.Equal(r.FindString("value"), "\"edited string\""); + +*/ + } + } +} +/* +Send("49-exec-continue"); +r = Expect("*stopped"); +Assert.Equal("exited", r.FindString("reason")); +*/ diff --git a/tests/SetValuesTest/SetValuesTest.csproj b/tests/SetValuesTest/SetValuesTest.csproj new file mode 100644 index 0000000..8e047a0 --- /dev/null +++ b/tests/SetValuesTest/SetValuesTest.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp2.0 + + + diff --git a/tests/runner/Runner.cs b/tests/runner/Runner.cs index 47f820b..b690d30 100644 --- a/tests/runner/Runner.cs +++ b/tests/runner/Runner.cs @@ -36,6 +36,9 @@ namespace Runner [Fact] public void BreakpointAddRemoveTest() => ExecuteTest(); + [Fact] + public void SetValuesTest() => ExecuteTest(); + private const int DefaultTimeoutSec = 20; private int expectTimeoutSec; diff --git a/tests/tests.sln b/tests/tests.sln index c429a10..9a23f3e 100644 --- a/tests/tests.sln +++ b/tests/tests.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExceptionTest", "ExceptionT EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BreakpointAddRemoveTest", "BreakpointAddRemoveTest\BreakpointAddRemoveTest.csproj", "{48403D8A-08D8-48FC-BF1B-F0239154AD85}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetValuesTest", "SetValuesTest\SetValuesTest.csproj", "{9E6CD7E6-0B84-4088-9097-1839A14B49DB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -86,5 +88,17 @@ Global {48403D8A-08D8-48FC-BF1B-F0239154AD85}.Release|x64.Build.0 = Release|x64 {48403D8A-08D8-48FC-BF1B-F0239154AD85}.Release|x86.ActiveCfg = Release|x86 {48403D8A-08D8-48FC-BF1B-F0239154AD85}.Release|x86.Build.0 = Release|x86 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x64.ActiveCfg = Debug|x64 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x64.Build.0 = Debug|x64 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x86.ActiveCfg = Debug|x86 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x86.Build.0 = Debug|x86 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|Any CPU.Build.0 = Release|Any CPU + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x64.ActiveCfg = Release|x64 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x64.Build.0 = Release|x64 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x86.ActiveCfg = Release|x86 + {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x86.Build.0 = Release|x86 EndGlobalSection EndGlobal