using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis;
using System.Reflection;
+using System.Dynamic;
+using Microsoft.CodeAnalysis.CSharp;
+using System.Text;
namespace SOS
{
- internal class SymbolReader
+ public class SymbolReader
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DebugInfo
}
}
+ private static string[] basicTypes = new string[] {
+ "System.Object",
+ "System.Boolean",
+ "System.Byte",
+ "System.SByte",
+ "System.Char",
+ "System.Double",
+ "System.Single",
+ "System.Int32",
+ "System.UInt32",
+ "System.Int64",
+ "System.UInt64",
+ "System.Int16",
+ "System.UInt16",
+ "System.IntPtr",
+ "System.UIntPtr",
+ "System.Decimal",
+ "System.String"
+ };
+
+ internal delegate bool GetChildDelegate(IntPtr opaque, IntPtr corValue, string name, out int dataTypeId, out IntPtr dataPtr);
+
+ private static GetChildDelegate getChild;
+
+ internal static void RegisterGetChild(GetChildDelegate cb)
+ {
+ getChild = cb;
+ }
+
+ public class ContextVariable : DynamicObject
+ {
+ private IntPtr m_opaque;
+ public IntPtr m_corValue { get; }
+
+ public ContextVariable(IntPtr opaque, IntPtr corValue)
+ {
+ this.m_opaque = opaque;
+ this.m_corValue = corValue;
+ }
+
+ private bool UnmarshalResult(int dataTypeId, IntPtr dataPtr, out object result)
+ {
+ if (dataTypeId < 0)
+ {
+ result = new ContextVariable(m_opaque, dataPtr);
+ return true;
+ }
+ if (dataTypeId == 0) // special case for null object
+ {
+ result = null;
+ return true;
+ }
+ if (dataTypeId >= basicTypes.Length)
+ {
+ result = null;
+ return false;
+ }
+ Type dataType = Type.GetType(basicTypes[dataTypeId]);
+ if (dataType == typeof(string))
+ {
+ if (dataPtr == IntPtr.Zero)
+ {
+ result = string.Empty;
+ return true;
+ }
+ result = Marshal.PtrToStringBSTR(dataPtr);
+ Marshal.FreeBSTR(dataPtr);
+ return true;
+ }
+ if (dataType == typeof(char))
+ {
+ BlittableChar c = Marshal.PtrToStructure<BlittableChar>(dataPtr);
+ Marshal.FreeCoTaskMem(dataPtr);
+ result = (char)c;
+ return true;
+ }
+ if (dataType == typeof(bool))
+ {
+ BlittableBoolean b = Marshal.PtrToStructure<BlittableBoolean>(dataPtr);
+ Marshal.FreeCoTaskMem(dataPtr);
+ result = (bool)b;
+ return true;
+ }
+ result = Marshal.PtrToStructure(dataPtr, dataType);
+ Marshal.FreeCoTaskMem(dataPtr);
+ return true;
+ }
+
+ public override bool TryGetMember(
+ GetMemberBinder binder, out object result)
+ {
+ IntPtr dataPtr;
+ int dataTypeId;
+ if (!getChild(m_opaque, m_corValue, binder.Name, out dataTypeId, out dataPtr))
+ {
+ result = null;
+ return false;
+ }
+ return UnmarshalResult(dataTypeId, dataPtr, out result);
+ }
+
+ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
+ {
+ IntPtr dataPtr;
+ int dataTypeId;
+ if (!getChild(m_opaque, m_corValue, "[" + string.Join(", ", indexes) + "]", out dataTypeId, out dataPtr))
+ {
+ result = null;
+ return false;
+ }
+ return UnmarshalResult(dataTypeId, dataPtr, out result);
+ }
+ }
+
+ public class Globals
+ {
+ public dynamic __context;
+ }
+
+ // Stores unresolved symbols, now only variables are supported
+ // Symbols are unique in list
+ class SyntaxAnalyzer
+ {
+ class FrameVars
+ {
+ Stack<string> vars = new Stack<string>();
+ Stack<int> varsInFrame = new Stack<int>();
+ int curFrameVars = 0;
+
+ public void Add(string name)
+ {
+ vars.Push(name);
+ curFrameVars++;
+ }
+
+ public void NewFrame()
+ {
+ varsInFrame.Push(curFrameVars);
+ curFrameVars = 0;
+ }
+
+ public void ExitFrame()
+ {
+ for (int i = 0; i < curFrameVars; i++)
+ vars.Pop();
+
+ curFrameVars = varsInFrame.Pop();
+ }
+
+ public bool Contains(string name)
+ {
+ return vars.Contains(name);
+ }
+ }
+
+ enum ParsingState
+ {
+ Common,
+ InvocationExpression,
+ GenericName
+ };
+
+ public List<string> unresolvedSymbols { get; private set; } = new List<string>();
+ FrameVars frameVars = new FrameVars();
+ SyntaxTree tree;
+
+ public SyntaxAnalyzer(string expression)
+ {
+ tree = CSharpSyntaxTree.ParseText(expression, options: new CSharpParseOptions(kind: SourceCodeKind.Script));
+ var root = tree.GetCompilationUnitRoot();
+ foreach (SyntaxNode sn in root.ChildNodes())
+ ParseNode(sn, ParsingState.Common);
+ }
+
+ void ParseAccessNode(SyntaxNode sn, ParsingState state)
+ {
+ SyntaxNodeOrToken snt = sn.ChildNodesAndTokens().First();
+
+ if (snt.Kind().Equals(SyntaxKind.SimpleMemberAccessExpression))
+ ParseAccessNode(snt.AsNode(), state);
+ else if (snt.IsNode)
+ ParseNode(snt.AsNode(), state);
+ else if (snt.IsToken)
+ ParseCommonToken(snt.AsToken(), state);
+ }
+
+ void ParseBlock(SyntaxNode sn, ParsingState state)
+ {
+ frameVars.NewFrame();
+ foreach (SyntaxNode snc in sn.ChildNodes())
+ ParseNode(sn, ParsingState.Common);
+ frameVars.ExitFrame();
+ }
+
+ void ParseNode(SyntaxNode sn, ParsingState state)
+ {
+ if (sn.Kind().Equals(SyntaxKind.InvocationExpression))
+ state = ParsingState.InvocationExpression;
+ else if (sn.Kind().Equals(SyntaxKind.GenericName))
+ state = ParsingState.GenericName;
+ else if (sn.Kind().Equals(SyntaxKind.ArgumentList))
+ state = ParsingState.Common;
+
+ foreach (SyntaxNodeOrToken snt in sn.ChildNodesAndTokens())
+ {
+ if (snt.IsNode)
+ {
+ if (snt.Kind().Equals(SyntaxKind.SimpleMemberAccessExpression))
+ ParseAccessNode(snt.AsNode(), state);
+ else if (snt.Kind().Equals(SyntaxKind.Block))
+ ParseBlock(snt.AsNode(), state);
+ else
+ ParseNode(snt.AsNode(), state);
+ }
+ else
+ {
+ if (sn.Kind().Equals(SyntaxKind.VariableDeclarator))
+ ParseDeclarator(snt.AsToken());
+ else
+ ParseCommonToken(snt.AsToken(), state);
+ }
+ }
+ }
+
+ void ParseCommonToken(SyntaxToken st, ParsingState state)
+ {
+ if (state == ParsingState.InvocationExpression ||
+ state == ParsingState.GenericName)
+ return;
+
+ if (st.Kind().Equals(SyntaxKind.IdentifierToken) &&
+ !unresolvedSymbols.Contains(st.Value.ToString()) &&
+ !frameVars.Contains(st.Value.ToString()))
+ unresolvedSymbols.Add(st.Value.ToString());
+ }
+
+ void ParseDeclarator(SyntaxToken st)
+ {
+ if (st.Kind().Equals(SyntaxKind.IdentifierToken) && !frameVars.Contains(st.Value.ToString()))
+ frameVars.Add(st.Value.ToString());
+ }
+ };
+
+
+ static void MarshalValue(object value, out int size, out IntPtr data)
+ {
+ if (value is string)
+ {
+ data = Marshal.StringToBSTR(value as string);
+ size = 0;
+ }
+ else if (value is char)
+ {
+ BlittableChar c = (BlittableChar)((char)value);
+ size = Marshal.SizeOf(c);
+ data = Marshal.AllocCoTaskMem(size);
+ Marshal.StructureToPtr(c, data, false);
+ }
+ else if (value is bool)
+ {
+ BlittableBoolean b = (BlittableBoolean)((bool)value);
+ size = Marshal.SizeOf(b);
+ data = Marshal.AllocCoTaskMem(size);
+ Marshal.StructureToPtr(b, data, false);
+ }
+ else
+ {
+ size = Marshal.SizeOf(value);
+ data = Marshal.AllocCoTaskMem(size);
+ Marshal.StructureToPtr(value, data, false);
+ }
+ }
+
+ internal static bool EvalExpression(string expr, IntPtr opaque, out IntPtr errorText, out int typeId, out int size, out IntPtr result)
+ {
+ SyntaxAnalyzer sa = new SyntaxAnalyzer(expr);
+
+ StringBuilder scriptText = new StringBuilder("#line hidden\n");
+
+ // Generate prefix with variables assignment to __context members
+ foreach (string us in sa.unresolvedSymbols)
+ scriptText.AppendFormat("var {0} = __context.{0};\n", us);
+
+ scriptText.Append("#line 1\n");
+ scriptText.Append(expr);
+
+ errorText = IntPtr.Zero;
+ result = IntPtr.Zero;
+ typeId = 0;
+ size = 0;
+ try
+ {
+ var scriptOptions = ScriptOptions.Default
+ .WithImports("System")
+ .WithReferences(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly);
+ var script = CSharpScript.Create(scriptText.ToString(), scriptOptions, globalsType: typeof(Globals));
+ script.Compile();
+ var returnValue = script.RunAsync(new Globals { __context = new ContextVariable(opaque, IntPtr.Zero) }).Result.ReturnValue;
+ if (returnValue is ContextVariable)
+ {
+ typeId = -1;
+ result = (returnValue as ContextVariable).m_corValue;
+ }
+ else
+ {
+ if (returnValue is null)
+ {
+ typeId = 0;
+ return true;
+ }
+ for (int i = 1; i < basicTypes.Length; i++)
+ {
+ if (returnValue.GetType() == Type.GetType(basicTypes[i]))
+ {
+ typeId = i;
+ MarshalValue(returnValue, out size, out result);
+ return true;
+ }
+ }
+ return false;
+ //errorText = Marshal.StringToBSTR(String.Format("{0}", returnValue));
+ }
+ }
+ catch(Exception e)
+ {
+ errorText = Marshal.StringToBSTR(e.ToString());
+ return false;
+ }
+ return true;
+ }
+
internal static bool ParseExpression(string expr, string resultTypeName, out IntPtr data, out int size, out IntPtr errorText)
{
object value = null;
errorText = Marshal.StringToBSTR("Value can not be null");
return false;
}
- if (value is string)
- {
- data = Marshal.StringToBSTR(value as string);
- }
- else if (value is char)
- {
- BlittableChar c = (BlittableChar)((char)value);
- size = Marshal.SizeOf(c);
- data = Marshal.AllocCoTaskMem(size);
- Marshal.StructureToPtr(c, data, false);
- }
- else if (value is bool)
- {
- BlittableBoolean b = (BlittableBoolean)((bool)value);
- size = Marshal.SizeOf(b);
- data = Marshal.AllocCoTaskMem(size);
- Marshal.StructureToPtr(b, data, false);
- }
- else
- {
- size = Marshal.SizeOf(value);
- data = Marshal.AllocCoTaskMem(size);
- Marshal.StructureToPtr(value, data, false);
- }
+ MarshalValue(value, out size, out data);
return true;
}
}
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Library</OutputType>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <NoStdLib>true</NoStdLib>
+ <!--NoStdLib>true</NoStdLib>
<NoCompilerStandardLib>true</NoCompilerStandardLib>
<IsDotNetFrameworkProductAssembly>true</IsDotNetFrameworkProductAssembly>
<AssemblyKey>Open</AssemblyKey>
<ExcludeMscorlibFacade>true</ExcludeMscorlibFacade>
- <ContainsPackageReferences>true</ContainsPackageReferences>
+ <ContainsPackageReferences>true</ContainsPackageReferences-->
</PropertyGroup>
<ItemGroup>
virtual HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) = 0;
virtual HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) = 0;
virtual int GetNamedVariables(uint32_t variablesReference) = 0;
- virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) = 0;
+ virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) = 0;
virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) = 0;
virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value, std::string &output) = 0;
};
const std::string &value,
std::string &output);
+ static BOOL VarGetChild(void *opaque, uint32_t varRef, const char* name, int *typeId, void **data);
+ bool GetChildDataByName(uint32_t varRef, const std::string &name, int *typeId, void **data);
+
public:
Variables(Evaluator &evaluator) : m_evaluator(evaluator), m_nextVariableReference(1) {}
HRESULT GetScopes(ICorDebugProcess *pProcess, uint64_t frameId, std::vector<Scope> &scopes);
- HRESULT Evaluate(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, Variable &variable);
+ HRESULT Evaluate(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, Variable &variable, std::string &output);
HRESULT GetValueByExpression(
ICorDebugProcess *pProcess,
HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) override;
HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) override;
int GetNamedVariables(uint32_t variablesReference) override;
- HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) override;
+ HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) override;
HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) override;
HRESULT SetVariableByExpression(uint64_t frameId, const std::string &expression, const std::string &value, std::string &output) override;
};
uint64_t frameId = StackFrame(threadId, level, "").id;
Variable variable;
- IfFailRet(m_debugger->Evaluate(frameId, expression, variable));
+ IfFailRet(m_debugger->Evaluate(frameId, expression, variable, output));
int print_values = 1;
PrintNewVar(varobjName, variable, threadId, print_values, output);
GetStepRangesFromIPDelegate SymbolReader::getStepRangesFromIPDelegate;
GetSequencePointsDelegate SymbolReader::getSequencePointsDelegate;
ParseExpressionDelegate SymbolReader::parseExpressionDelegate = nullptr;
+EvalExpressionDelegate SymbolReader::evalExpressionDelegate = nullptr;
+RegisterGetChildDelegate SymbolReader::registerGetChildDelegate = nullptr;
SysAllocStringLen_t SymbolReader::sysAllocStringLen;
SysFreeString_t SymbolReader::sysFreeString;
SysStringLen_t SymbolReader::sysStringLen;
+CoTaskMemAlloc_t SymbolReader::coTaskMemAlloc;
CoTaskMemFree_t SymbolReader::coTaskMemFree;
const int SymbolReader::HiddenLine = 0xfeefee;
return Status;
}
+struct GetChildProxy
+{
+ SymbolReader::GetChildCallback &m_cb;
+ static BOOL GetChild(PVOID opaque, PVOID corValue, const char* name, int *typeId, PVOID *data)
+ {
+ return static_cast<GetChildProxy*>(opaque)->m_cb(corValue, name, typeId, data);
+ }
+};
+
HRESULT SymbolReader::PrepareSymbolReader()
{
static bool attemptedSymbolReaderPreparation = false;
sysAllocStringLen = (SysAllocStringLen_t)DLSym(coreclrLib, "SysAllocStringLen");
sysFreeString = (SysFreeString_t)DLSym(coreclrLib, "SysFreeString");
sysStringLen = (SysStringLen_t)DLSym(coreclrLib, "SysStringLen");
+ coTaskMemAlloc = (CoTaskMemAlloc_t)DLSym(coreclrLib, "CoTaskMemAlloc");
coTaskMemFree = (CoTaskMemFree_t)DLSym(coreclrLib, "CoTaskMemFree");
#else
sysAllocStringLen = SysAllocStringLen;
sysFreeString = SysFreeString;
sysStringLen = SysStringLen;
+ coTaskMemAlloc = CoTaskMemAlloc;
coTaskMemFree = CoTaskMemFree;
#endif
return E_FAIL;
}
+ if (coTaskMemAlloc == nullptr)
+ {
+ fprintf(stderr, "Error: CoTaskMemAlloc not found\n");
+ return E_FAIL;
+ }
+
if (coTaskMemFree == nullptr)
{
fprintf(stderr, "Error: CoTaskMemFree not found\n");
IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetStepRangesFromIP", (void **)&getStepRangesFromIPDelegate));
IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetSequencePoints", (void **)&getSequencePointsDelegate));
IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "ParseExpression", (void **)&parseExpressionDelegate));
+ IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "EvalExpression", (void **)&evalExpressionDelegate));
+ IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "RegisterGetChild", (void **)®isterGetChildDelegate));
+ if (!registerGetChildDelegate(GetChildProxy::GetChild))
+ return E_FAIL;
// Warm up Roslyn
std::thread([](){ std::string data; std::string err; SymbolReader::ParseExpression("1", "System.Int32", data, err); }).detach();
return S_OK;
}
+
+HRESULT SymbolReader::EvalExpression(const std::string &expr, std::string &result, int *typeId, ICorDebugValue **ppValue, GetChildCallback cb)
+{
+ HRESULT Status;
+
+ PrepareSymbolReader();
+
+ if (evalExpressionDelegate == nullptr)
+ return E_FAIL;
+
+ GetChildProxy proxy { cb };
+
+ PVOID valuePtr = nullptr;
+ int size = 0;
+ BSTR resultText;
+ BOOL ok = evalExpressionDelegate(expr.c_str(), &proxy, &resultText, typeId, &size, &valuePtr);
+ if (!ok)
+ {
+ if (resultText)
+ {
+ result = to_utf8(resultText);
+ sysFreeString(resultText);
+ }
+ return E_FAIL;
+ }
+
+ switch(*typeId)
+ {
+ case TypeCorValue:
+ *ppValue = static_cast<ICorDebugValue*>(valuePtr);
+ if (*ppValue)
+ (*ppValue)->AddRef();
+ break;
+ case TypeObject:
+ result = std::string();
+ break;
+ case TypeString:
+ result = to_utf8((BSTR)valuePtr);
+ sysFreeString((BSTR)valuePtr);
+ break;
+ default:
+ result.resize(size);
+ memmove(&result[0], valuePtr, size);
+ coTaskMemFree(valuePtr);
+ break;
+ }
+
+ return S_OK;
+}
+
+PVOID SymbolReader::AllocBytes(size_t size)
+{
+ PrepareSymbolReader();
+ if (coTaskMemAlloc == nullptr)
+ return nullptr;
+ return coTaskMemAlloc(size);
+}
+
+PVOID SymbolReader::AllocString(const std::string &str)
+{
+ PrepareSymbolReader();
+ if (sysAllocStringLen == nullptr)
+ return nullptr;
+ auto wstr = to_utf16(str);
+ BSTR bstr = sysAllocStringLen(0, wstr.size());
+ if (sysStringLen(bstr) == 0)
+ return nullptr;
+ memmove(bstr, wstr.data(), wstr.size() * sizeof(decltype(wstr[0])));
+ return bstr;
+}
#include <string>
#include <vector>
+#include <functional>
/// FIXME: Definition of `TADDR`
typedef BOOL (*GetStepRangesFromIPDelegate)(PVOID, int, mdMethodDef, unsigned int*, unsigned int*);
typedef BOOL (*GetSequencePointsDelegate)(PVOID, mdMethodDef, PVOID*, int*);
typedef BOOL (*ParseExpressionDelegate)(const char*, const char*, PVOID*, int *, BSTR*);
+typedef BOOL (*EvalExpressionDelegate)(const char*, PVOID, BSTR*, int*, int*, PVOID*);
+typedef BOOL (*GetChildDelegate)(PVOID, PVOID, const char*, int *, PVOID*);
+typedef BOOL (*RegisterGetChildDelegate)(GetChildDelegate);
typedef BSTR (*SysAllocStringLen_t)(const OLECHAR*, UINT);
typedef void (*SysFreeString_t)(BSTR);
typedef UINT (*SysStringLen_t)(BSTR);
+typedef LPVOID (*CoTaskMemAlloc_t)(size_t);
typedef void (*CoTaskMemFree_t)(LPVOID);
BOOL SafeReadMemory (TADDR offset, PVOID lpBuffer, ULONG cb,
static GetStepRangesFromIPDelegate getStepRangesFromIPDelegate;
static GetSequencePointsDelegate getSequencePointsDelegate;
static ParseExpressionDelegate parseExpressionDelegate;
+ static EvalExpressionDelegate evalExpressionDelegate;
+ static RegisterGetChildDelegate registerGetChildDelegate;
static SysAllocStringLen_t sysAllocStringLen;
static SysFreeString_t sysFreeString;
static SysStringLen_t sysStringLen;
+ static CoTaskMemAlloc_t coTaskMemAlloc;
static CoTaskMemFree_t coTaskMemFree;
static HRESULT PrepareSymbolReader();
int32_t offset;
} PACK_END;
+ // Keep in sync with string[] basicTypes in SymbolReader.cs
+ enum BasicTypes {
+ TypeCorValue = -1,
+ TypeObject = 0, // "System.Object",
+ TypeBoolean, // "System.Boolean",
+ TypeByte, // "System.Byte",
+ TypeSByte, // "System.SByte",
+ TypeChar, // "System.Char",
+ TypeDouble, // "System.Double",
+ TypeSingle, // "System.Single",
+ TypeInt32, // "System.Int32",
+ TypeUInt32, // "System.UInt32",
+ TypeInt64, // "System.Int64",
+ TypeUInt64, // "System.UInt64",
+ TypeInt16, // "System.Int16",
+ TypeUInt16, // "System.UInt16",
+ TypeIntPtr, // "System.IntPtr",
+ TypeUIntPtr, // "System.UIntPtr",
+ TypeDecimal, // "System.Decimal",
+ TypeString, // "System.String"
+ };
+
SymbolReader()
{
m_symbolReaderHandle = 0;
static void SetCoreCLRPath(const std::string &path) { coreClrPath = path; }
+ typedef std::function<bool(PVOID, const std::string&, int *, PVOID*)> GetChildCallback;
+
HRESULT LoadSymbols(IMetaDataImport* pMD, ICorDebugModule* pModule);
HRESULT GetLineByILOffset(mdMethodDef MethodToken, ULONG64 IlOffset, ULONG *pLinenum, WCHAR* pwszFileName, ULONG cchFileName);
HRESULT GetNamedLocalVariableAndScope(ICorDebugILFrame * pILFrame, mdMethodDef methodToken, ULONG localIndex, WCHAR* paramName, ULONG paramNameLen, ICorDebugValue **ppValue, ULONG32* pIlStart, ULONG32* pIlEnd);
HRESULT GetStepRangesFromIP(ULONG32 ip, mdMethodDef MethodToken, ULONG32 *ilStartOffset, ULONG32 *ilEndOffset);
HRESULT GetSequencePoints(mdMethodDef methodToken, std::vector<SequencePoint> &points);
static HRESULT ParseExpression(const std::string &expr, const std::string &typeName, std::string &data, std::string &errorText);
+ static HRESULT EvalExpression(const std::string &expr, std::string &result, int *typeId, ICorDebugValue **ppValue, GetChildCallback cb);
+ static PVOID AllocBytes(size_t size);
+ static PVOID AllocString(const std::string &str);
};
#include "typeprinter.h"
#include "torelease.h"
#include "cputil.h"
+#include "symbolreader.h"
// From strike.cpp
return result;
}
-static HRESULT PrintDecimalValue(ICorDebugValue *pValue,
- std::string &output)
+static void PrintDecimal(
+ unsigned int hi,
+ unsigned int mid,
+ unsigned int lo,
+ unsigned int flags,
+ std::string &output)
{
- HRESULT Status = S_OK;
-
- unsigned int hi;
- unsigned int mid;
- unsigned int lo;
- unsigned int flags;
- IfFailRet(GetDecimalFields(pValue, hi, mid, lo, flags));
-
uint32_t v[3] = { lo, mid, hi };
output = uint96_to_string(v);
if (is_negative)
output.insert(0, 1, '-');
+}
+
+static HRESULT PrintDecimalValue(ICorDebugValue *pValue,
+ std::string &output)
+{
+ HRESULT Status = S_OK;
+
+ unsigned int hi;
+ unsigned int mid;
+ unsigned int lo;
+ unsigned int flags;
+
+ IfFailRet(GetDecimalFields(pValue, hi, mid, lo, flags));
+
+ PrintDecimal(hi, mid, lo, flags, output);
return S_OK;
}
+PACK_BEGIN struct Decimal {
+ uint32_t flags;
+ uint32_t hi;
+ uint32_t lo;
+ uint32_t mid;
+} PACK_END;
+
+static void PrintDecimalValue(const std::string &rawValue,
+ std::string &output)
+{
+ const Decimal *d = reinterpret_cast<const Decimal*>(&rawValue[0]);
+ PrintDecimal(d->hi, d->mid, d->lo, d->flags, output);
+}
+
static HRESULT PrintArrayValue(ICorDebugValue *pValue,
std::string &output)
{
output = ss.str();
return S_OK;
}
+
+HRESULT PrintBasicValue(int typeId, const std::string &rawData, std::string &typeName, std::string &value)
+{
+ std::ostringstream ss;
+ switch(typeId)
+ {
+ case SymbolReader::TypeCorValue:
+ ss << "null";
+ typeName = "object";
+ break;
+ case SymbolReader::TypeObject:
+ ss << "null";
+ typeName = "object";
+ break;
+ case SymbolReader::TypeBoolean:
+ ss << (rawData[0] == 0 ? "false" : "true");
+ typeName = "bool";
+ break;
+ case SymbolReader::TypeByte:
+ ss << (unsigned int) *(unsigned char*) &(rawData[0]);
+ typeName = "byte";
+ break;
+ case SymbolReader::TypeSByte:
+ ss << (int) *(char*) &(rawData[0]);
+ typeName = "sbyte";
+ break;
+ case SymbolReader::TypeChar:
+ {
+ WCHAR wc = * (WCHAR *) &(rawData[0]);
+ std::string printableVal = to_utf8(wc);
+ EscapeString(printableVal, '\'');
+ ss << (unsigned int)wc << " '" << printableVal << "'";
+ typeName = "char";
+ }
+ break;
+ case SymbolReader::TypeDouble:
+ ss << std::setprecision(16) << *(double*) &(rawData[0]);
+ typeName = "double";
+ break;
+ case SymbolReader::TypeSingle:
+ ss << std::setprecision(8) << *(float*) &(rawData[0]);
+ typeName = "float";
+ break;
+ case SymbolReader::TypeInt32:
+ ss << *(int*) &(rawData[0]);
+ typeName = "int";
+ break;
+ case SymbolReader::TypeUInt32:
+ ss << *(unsigned int*) &(rawData[0]);
+ typeName = "uint";
+ break;
+ case SymbolReader::TypeInt64:
+ ss << *(__int64*) &(rawData[0]);
+ typeName = "long";
+ break;
+ case SymbolReader::TypeUInt64:
+ typeName = "ulong";
+ ss << *(unsigned __int64*) &(rawData[0]);
+ break;
+ case SymbolReader::TypeInt16:
+ ss << *(short*) &(rawData[0]);
+ typeName = "short";
+ break;
+ case SymbolReader::TypeUInt16:
+ ss << *(unsigned short*) &(rawData[0]);
+ typeName = "ushort";
+ break;
+ case SymbolReader::TypeIntPtr:
+ ss << "0x" << std::hex << *(intptr_t*) &(rawData[0]);
+ typeName = "IntPtr";
+ break;
+ case SymbolReader::TypeUIntPtr:
+ ss << "0x" << std::hex << *(intptr_t*) &(rawData[0]);
+ typeName = "UIntPtr";
+ break;
+ case SymbolReader::TypeDecimal:
+ PrintDecimalValue(rawData, value);
+ typeName = "decimal";
+ return S_OK;
+ case SymbolReader::TypeString:
+ {
+ std::string rawStr = rawData;
+ EscapeString(rawStr, '"');
+ ss << "\"" << rawStr << "\"";
+ }
+ break;
+ }
+ value = ss.str();
+ return S_OK;
+}
+
+HRESULT MarshalValue(ICorDebugValue *pInputValue, int *typeId, void **data)
+{
+ HRESULT Status;
+
+ BOOL isNull = TRUE;
+ ToRelease<ICorDebugValue> pValue;
+ IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull));
+
+ if (isNull)
+ {
+ *data = nullptr;
+ *typeId = SymbolReader::TypeObject;
+ return S_OK;
+ }
+
+ ULONG32 cbSize;
+ IfFailRet(Status = pValue->GetSize(&cbSize));
+
+ ArrayHolder<BYTE> rgbValue = new (std::nothrow) BYTE[cbSize];
+ if (rgbValue == nullptr)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE));
+
+ CorElementType corElemType;
+ IfFailRet(pValue->GetType(&corElemType));
+
+ if (corElemType == ELEMENT_TYPE_STRING)
+ {
+ std::string raw_str;
+ IfFailRet(PrintStringValue(pValue, raw_str));
+
+ if (!raw_str.empty())
+ {
+ *data = SymbolReader::AllocString(raw_str);
+ if (*data == nullptr)
+ return E_FAIL;
+ }
+ else
+ {
+ *data = nullptr;
+ }
+
+ *typeId = SymbolReader::TypeString;
+ return S_OK;
+ }
+
+ if (corElemType == ELEMENT_TYPE_SZARRAY || corElemType == ELEMENT_TYPE_ARRAY)
+ {
+ pInputValue->AddRef();
+ *data = pInputValue;
+ *typeId = SymbolReader::TypeCorValue;
+ return S_OK;
+ }
+
+ ToRelease<ICorDebugGenericValue> pGenericValue;
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue));
+ IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0])));
+
+ if (IsEnum(pValue))
+ {
+ return E_FAIL;
+ // TODO: Support enums, return PrintEnumValue(pValue, rgbValue, output);
+ }
+
+ switch (corElemType)
+ {
+ default:
+ return E_FAIL;
+
+ case ELEMENT_TYPE_PTR:
+ *typeId = SymbolReader::TypeIntPtr;
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ CORDB_ADDRESS addr = 0;
+ ToRelease<ICorDebugReferenceValue> pReferenceValue;
+ if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue)))
+ pReferenceValue->GetValue(&addr);
+ *(CORDB_ADDRESS*) &(rgbValue[0]) = addr;
+ *typeId = SymbolReader::TypeIntPtr;
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ {
+ std::string typeName;
+ TypePrinter::GetTypeOfValue(pValue, typeName);
+ if (typeName != "decimal")
+ {
+ pInputValue->AddRef();
+ *data = pInputValue;
+ *typeId = SymbolReader::TypeCorValue;
+ return S_OK;
+ }
+ *typeId = SymbolReader::TypeDecimal;
+ }
+ break;
+
+ case ELEMENT_TYPE_BOOLEAN:
+ *typeId = SymbolReader::TypeBoolean;
+ break;
+
+ case ELEMENT_TYPE_CHAR:
+ *typeId = SymbolReader::TypeChar;
+ break;
+
+ case ELEMENT_TYPE_I1:
+ *typeId = SymbolReader::TypeSByte;
+ break;
+
+ case ELEMENT_TYPE_U1:
+ *typeId = SymbolReader::TypeByte;
+ break;
+
+ case ELEMENT_TYPE_I2:
+ *typeId = SymbolReader::TypeInt16;
+ break;
+
+ case ELEMENT_TYPE_U2:
+ *typeId = SymbolReader::TypeUInt16;
+ break;
+
+ case ELEMENT_TYPE_I:
+ *typeId = SymbolReader::TypeIntPtr;
+ break;
+
+ case ELEMENT_TYPE_U:
+ *typeId = SymbolReader::TypeUIntPtr;
+ break;
+
+ case ELEMENT_TYPE_I4:
+ *typeId = SymbolReader::TypeInt32;
+ break;
+
+ case ELEMENT_TYPE_U4:
+ *typeId = SymbolReader::TypeUInt32;
+ break;
+
+ case ELEMENT_TYPE_I8:
+ *typeId = SymbolReader::TypeInt64;
+ break;
+
+ case ELEMENT_TYPE_U8:
+ *typeId = SymbolReader::TypeUInt64;
+ break;
+
+ case ELEMENT_TYPE_R4:
+ *typeId = SymbolReader::TypeSingle;
+ break;
+
+ case ELEMENT_TYPE_R8:
+ *typeId = SymbolReader::TypeDouble;
+ break;
+
+ case ELEMENT_TYPE_OBJECT:
+ return E_FAIL;
+
+ }
+
+ *data = SymbolReader::AllocBytes(cbSize);
+ if (*data == nullptr)
+ {
+ return E_FAIL;
+ }
+ memmove(*data, &(rgbValue[0]), cbSize);
+ return S_OK;
+}
#include <string>
HRESULT PrintValue(ICorDebugValue *pInputValue, std::string &output, bool escape = true);
+HRESULT PrintBasicValue(int typeId, const std::string &rawData, std::string &typeName, std::string &value);
HRESULT DereferenceAndUnboxValue(ICorDebugValue * pValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = nullptr);
+HRESULT MarshalValue(ICorDebugValue *pInputValue, int *typeId, void **data);
#include <unordered_set>
#include <vector>
#include <cstring>
-
+#include <algorithm>
#include <sstream>
#include "typeprinter.h"
#include "valueprint.h"
#include "valuewrite.h"
#include "frames.h"
+#include "symbolreader.h"
HRESULT Variables::GetNumChild(
return S_OK;
}
-HRESULT ManagedDebugger::Evaluate(uint64_t frameId, const std::string &expression, Variable &variable)
+HRESULT ManagedDebugger::Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output)
{
- return m_variables.Evaluate(m_pProcess, frameId, expression, variable);
+ return m_variables.Evaluate(m_pProcess, frameId, expression, variable, output);
}
HRESULT Variables::Evaluate(
ICorDebugProcess *pProcess,
uint64_t frameId,
const std::string &expression,
- Variable &variable)
+ Variable &variable,
+ std::string &output)
{
if (pProcess == nullptr)
return E_FAIL;
IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
ToRelease<ICorDebugFrame> pFrame;
IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+ ToRelease<ICorDebugILFrame> pILFrame;
+ if (pFrame)
+ IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame));
ToRelease<ICorDebugValue> pResultValue;
- IfFailRet(m_evaluator.EvalExpr(pThread, pFrame, expression, &pResultValue));
+ int typeId;
+ std::vector< ToRelease<ICorDebugValue> > marshalledValues;
+
+ IfFailRet(SymbolReader::EvalExpression(
+ expression, output, &typeId, &pResultValue,
+ [&](void *corValue, const std::string &name, int *typeId, void **data) -> bool
+ {
+ ToRelease<ICorDebugValue> pThisValue;
+
+ if (!corValue) // Scope
+ {
+ bool found = false;
+ if (FAILED(m_evaluator.WalkStackVars(pFrame, [&](
+ ICorDebugILFrame *pILFrame,
+ ICorDebugValue *pValue,
+ const std::string &varName) -> HRESULT
+ {
+ if (!found && varName == "this")
+ {
+ pThisValue = pValue;
+ pValue->AddRef();
+ }
+ if (!found && varName == name)
+ {
+ found = true;
+ IfFailRet(MarshalValue(pValue, typeId, data));
+ if (*typeId < 0)
+ {
+ marshalledValues.emplace_back(static_cast<ICorDebugValue*>(*data)); // FIXME: no exception safety
+ }
+ }
+
+ return S_OK;
+ })))
+ {
+ return false;
+ }
+ if (found)
+ return true;
+ if (!pThisValue)
+ return false;
+
+ corValue = pThisValue;
+ }
+
+ std::vector<Member> members;
+
+ const bool fetchOnlyStatic = false;
+ bool hasStaticMembers = false;
+
+ ICorDebugValue *pValue = static_cast<ICorDebugValue*>(corValue);
+
+ if (FAILED(FetchFieldsAndProperties(pValue,
+ pThread,
+ pILFrame,
+ members,
+ fetchOnlyStatic,
+ hasStaticMembers,
+ 0,
+ INT_MAX)))
+ return false;
+
+ FixupInheritedFieldNames(members);
+
+ auto memberIt = std::find_if(members.begin(), members.end(), [&name](const Member &m){ return m.name == name; });
+ if (memberIt == members.end())
+ return false;
+
+ if (!memberIt->value)
+ return false;
+
+ if (FAILED(MarshalValue(memberIt->value, typeId, data)))
+ {
+ return false;
+ }
+ if (*typeId < 0)
+ marshalledValues.emplace_back(static_cast<ICorDebugValue*>(*data));
+
+ return true;
+ }));
variable.evaluateName = expression;
- bool escape = true;
- PrintValue(pResultValue, variable.value, escape);
- TypePrinter::GetTypeOfValue(pResultValue, variable.type);
+ if (pResultValue)
+ {
+ const bool escape = true;
+ PrintValue(pResultValue, variable.value, escape);
+ TypePrinter::GetTypeOfValue(pResultValue, variable.type);
+ }
+ else
+ {
+ PrintBasicValue(typeId, output, variable.type, variable.value);
+ }
AddVariableReference(variable, frameId, pResultValue, ValueIsVariable);
return S_OK;
uint64_t frameId = frameIdIter.value();
Variable variable;
- IfFailRet(m_debugger->Evaluate(frameId, expression, variable));
+ std::string output;
+ Status = m_debugger->Evaluate(frameId, expression, variable, output);
+ if (FAILED(Status))
+ {
+ body["message"] = output;
+ return Status;
+ }
body["result"] = variable.value;
body["type"] = variable.type;
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+ <PropertyGroup>\r
+ <OutputType>Exe</OutputType>\r
+ <TargetFramework>netcoreapp2.0</TargetFramework>\r
+ </PropertyGroup>\r
+\r
+</Project>\r
--- /dev/null
+/*\r
+using System.IO;\r
+Send("1-file-exec-and-symbols dotnet");\r
+Send("2-exec-arguments " + TestBin);\r
+\r
+string filename = Path.GetFileName(TestSource);\r
+\r
+Send(String.Format("3-break-insert -f {0}:{1}", filename, Lines["BREAK1"]));\r
+r = Expect("3^done");\r
+int id1 = r.Find("bkpt").FindInt("number");\r
+\r
+Send(String.Format("4-break-insert -f {0}:{1}", filename, Lines["BREAK2"]));\r
+r = Expect("4^done");\r
+int id2 = r.Find("bkpt").FindInt("number");\r
+\r
+Send(String.Format("5-break-insert -f {0}:{1}", filename, Lines["BREAK3"]));\r
+r = Expect("5^done");\r
+int id3 = r.Find("bkpt").FindInt("number");\r
+\r
+Send("6-exec-run");\r
+*/\r
+\r
+using System;\r
+\r
+namespace ExpressionsTest\r
+{\r
+ class Program\r
+ {\r
+ static void Main(string[] args)\r
+ { // //@START@\r
+/*\r
+var r = Expect("*stopped");\r
+Assert.Equal("entry-point-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["START"], r.Find("frame").FindInt("line"));\r
+\r
+Send("7-exec-continue");\r
+*/\r
+ int a = 10;\r
+ int b = 11;\r
+ TestStruct tc = new TestStruct(a + 1, b);\r
+ string str1 = "string1";\r
+ string str2 = "string2";\r
+ int c = tc.b + b; // //@BREAK1@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK1"], r.Find("frame").FindInt("line"));\r
+Assert.Equal(id1, r.FindInt("bkptno"));\r
+\r
+Send(String.Format("8-var-create - * \"{0}\"", "a + b"));\r
+r = Expect("8^done");\r
+Assert.Equal("21", r.FindString("value"));\r
+\r
+Send(String.Format("9-var-create - * \"{0}\"", "tc.a + b"));\r
+r = Expect("9^done");\r
+Assert.Equal("22", r.FindString("value"));\r
+\r
+Send(String.Format("10-var-create - * \"{0}\"", "str1 + str2"));\r
+r = Expect("10^done");\r
+Assert.Equal("\"string1string2\"", r.FindString("value"));\r
+\r
+Send("11-exec-continue");\r
+*/\r
+ {\r
+ int d = 99;\r
+ int e = c + a; // //@BREAK2@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK2"], r.Find("frame").FindInt("line"));\r
+Assert.Equal(id2, r.FindInt("bkptno"));\r
+\r
+Send(String.Format("12-var-create - * \"{0}\"", "d + a"));\r
+r = Expect("12^done");\r
+Assert.Equal("109", r.FindString("value"));\r
+\r
+Send("13-exec-continue");\r
+*/\r
+ }\r
+\r
+ Console.WriteLine(str1 + str2);\r
+\r
+ tc.IncA();\r
+\r
+ Console.WriteLine("Hello World!");\r
+ }\r
+ }\r
+\r
+ struct TestStruct\r
+ {\r
+ public int a;\r
+ public int b;\r
+\r
+ public TestStruct(int x, int y)\r
+ {\r
+ a = x;\r
+ b = y;\r
+ }\r
+\r
+ public void IncA()\r
+ {\r
+ a++; // //@BREAK3@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK3"], r.Find("frame").FindInt("line"));\r
+Assert.Equal(id3, r.FindInt("bkptno"));\r
+\r
+Send(String.Format("14-var-create - * \"{0}\"", "a + 1"));\r
+r = Expect("14^done");\r
+Assert.Equal("12", r.FindString("value"));\r
+\r
+Send("15-exec-continue");\r
+*/\r
+ }\r
+ }\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("exited", r.FindString("reason"));\r
+*/\r
+}\r
[Fact]
public void SetValuesTest() => ExecuteTest();
+ [Fact]
+ public void ExpressionsTest() => ExecuteTest();
+
private const int DefaultTimeoutSec = 20;
private int expectTimeoutSec;
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetValuesTest", "SetValuesTest\SetValuesTest.csproj", "{9E6CD7E6-0B84-4088-9097-1839A14B49DB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionsTest", "ExpressionsTest\ExpressionsTest.csproj", "{3477E658-CB14-4D86-8909-F7448C17AC9A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
{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
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x64.ActiveCfg = Debug|x64
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x64.Build.0 = Debug|x64
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x86.ActiveCfg = Debug|x86
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x86.Build.0 = Debug|x86
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x64.ActiveCfg = Release|x64
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x64.Build.0 = Release|x64
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x86.ActiveCfg = Release|x86
+ {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x86.Build.0 = Release|x86
EndGlobalSection
EndGlobal