You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1772 lines
74 KiB
1772 lines
74 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Collections; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Threading; |
|
using ILRuntime.CLR.Method; |
|
using ILRuntime.Runtime.Intepreter; |
|
using ILRuntime.Runtime.Stack; |
|
using ILRuntime.CLR.Utils; |
|
using ILRuntime.Runtime.Intepreter.RegisterVM; |
|
using ILRuntime.Runtime.Debugger.Protocol; |
|
#if ILRUNTIME_ENABLE_ROSYLN |
|
using Microsoft.CodeAnalysis.CSharp; |
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|
#endif |
|
using System.Reflection; |
|
|
|
namespace ILRuntime.Runtime.Debugger |
|
{ |
|
public class DebugService |
|
{ |
|
BreakPointContext curBreakpoint; |
|
DebuggerServer server; |
|
Runtime.Enviorment.AppDomain domain; |
|
Dictionary<int, LinkedList<BreakpointInfo>> activeBreakpoints = new Dictionary<int, LinkedList<BreakpointInfo>>(); |
|
Dictionary<int, BreakpointInfo> breakpointMapping = new Dictionary<int, BreakpointInfo>(); |
|
Queue<Tuple<int, int, VariableReference>> pendingReferences = new Queue<Tuple<int, int, VariableReference>>(); |
|
Queue<Tuple<int, int, VariableReference>> pendingEnuming = new Queue<Tuple<int, int, VariableReference>>(); |
|
Queue<Tuple<int, int, VariableReference>> pendingIndexing = new Queue<Tuple<int, int, VariableReference>>(); |
|
AutoResetEvent evt = new AutoResetEvent(false); |
|
string breakpointParseCode = "void Method() {{ ({0}) }}"; // ()是因为a?b:c会被解析为可空类型a的类型变量声明,加小括号才会解析为条件表达式 |
|
public UsingInfo[] UsingInfosContext { get; set; } |
|
|
|
public Action<string> OnBreakPoint; |
|
public Action<string> OnILRuntimeException; |
|
|
|
public Enviorment.AppDomain AppDomain { get { return domain; } } |
|
|
|
public AutoResetEvent BlockEvent { get { return evt; } } |
|
|
|
public bool IsDebuggerAttached |
|
{ |
|
get |
|
{ |
|
#if DEBUG && !DISABLE_ILRUNTIME_DEBUG |
|
return (server != null && server.IsAttached); |
|
#else |
|
return false; |
|
#endif |
|
} |
|
} |
|
|
|
public DebugService(Runtime.Enviorment.AppDomain domain) |
|
{ |
|
this.domain = domain; |
|
} |
|
|
|
/// <summary> |
|
/// Start Debugger Server |
|
/// </summary> |
|
/// <param name="port">Port to listen on. default value is 56000</param> |
|
/// <param name="boardcastDebuggerInfo">true表示使用udp在参数port表示的端口上广播本地调试器信息,调试器使用动态端口。false表示本地调试器使用参数port表示的固定端口。默认值为true</param> |
|
/// <returns>错误描述,null表示没有错误</returns> |
|
public string StartDebugService(int port = 56000, bool boardcastDebuggerInfo = true) |
|
{ |
|
#if DEBUG && !DISABLE_ILRUNTIME_DEBUG |
|
server = new Debugger.DebuggerServer(this); |
|
server.Port = port; |
|
return server.Start(boardcastDebuggerInfo); |
|
#else |
|
return null; |
|
#endif |
|
} |
|
|
|
/// <summary> |
|
/// Stop Debugger Server |
|
/// </summary> |
|
public void StopDebugService() |
|
{ |
|
#if DEBUG && !DISABLE_ILRUNTIME_DEBUG |
|
if (server != null) |
|
server.Stop(); |
|
server = null; |
|
#endif |
|
} |
|
|
|
/// <summary> |
|
/// 中断运行 |
|
/// </summary> |
|
/// <param name="intpreter"></param> |
|
/// <param name="ex"></param> |
|
/// <returns>如果挂的有调试器则返回true</returns> |
|
internal bool Break(ILIntepreter intpreter, Exception ex = null) |
|
{ |
|
BreakPointContext ctx = new BreakPointContext(); |
|
ctx.Interpreter = intpreter; |
|
ctx.Exception = ex; |
|
|
|
curBreakpoint = ctx; |
|
|
|
if (OnBreakPoint != null) |
|
{ |
|
OnBreakPoint(ctx.DumpContext()); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
string GetInstructionDocument(Mono.Cecil.Cil.Instruction ins, Mono.Cecil.MethodDefinition md) |
|
{ |
|
if (ins != null) |
|
{ |
|
var seq = FindSequencePoint(ins, md.DebugInformation.GetSequencePointMapping()); |
|
if (seq != null) |
|
{ |
|
string path = seq.Document.Url.Replace("\\", "/"); |
|
return string.Format("(at {0}:{1})", path, seq.StartLine); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
public string GetStackTrace(ILIntepreter intepreper) |
|
{ |
|
StringBuilder sb = new StringBuilder(); |
|
ILRuntime.CLR.Method.ILMethod m; |
|
StackFrame[] frames = intepreper.Stack.Frames.ToArray(); |
|
Mono.Cecil.Cil.Instruction ins = null; |
|
RegisterVMSymbol vmSymbol; |
|
if (frames[0].Address != null) |
|
{ |
|
if (frames[0].IsRegister) |
|
{ |
|
frames[0].Method.RegisterVMSymbols.TryGetValue(frames[0].Address.Value, out vmSymbol); |
|
ins = vmSymbol.Instruction; |
|
sb.AppendLine(string.Format("{0}(JIT_{1:0000}:{2})", ins, frames[0].Address.Value, frames[0].Method.BodyRegister[frames[0].Address.Value])); |
|
} |
|
else |
|
{ |
|
ins = frames[0].Method.Definition.Body.Instructions[frames[0].Address.Value]; |
|
sb.AppendLine(ins.ToString()); |
|
} |
|
} |
|
for (int i = 0; i < frames.Length; i++) |
|
{ |
|
var f = frames[i]; |
|
m = f.Method; |
|
string document = ""; |
|
if (f.IsRegister) |
|
{ |
|
if (f.Address != null) |
|
{ |
|
if (f.Method.RegisterVMSymbols.TryGetValue(f.Address.Value, out vmSymbol)) |
|
{ |
|
RegisterVMSymbolLink link = null; |
|
do |
|
{ |
|
if (link != null) |
|
vmSymbol = link.Value; |
|
ins = vmSymbol.Instruction; |
|
var md = vmSymbol.Method.Definition; |
|
document = GetInstructionDocument(ins, md); |
|
sb.AppendFormat("at {0} {1}\r\n", vmSymbol.Method, document); |
|
link = vmSymbol.ParentSymbol; |
|
} |
|
while (link != null); |
|
} |
|
else |
|
sb.AppendFormat("at {0} {1}\r\n", m, document); |
|
} |
|
else |
|
sb.AppendFormat("at {0} {1}\r\n", m, document); |
|
} |
|
else |
|
{ |
|
if (f.Address != null) |
|
{ |
|
ins = m.Definition.Body.Instructions[f.Address.Value]; |
|
var md = m.Definition; |
|
|
|
document = GetInstructionDocument(ins, md); |
|
} |
|
sb.AppendFormat("at {0} {1}\r\n", m, document); |
|
} |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
public unsafe string GetThisInfo(ILIntepreter intepreter) |
|
{ |
|
var topFrame = intepreter.Stack.Frames.Peek(); |
|
var arg = Minus(topFrame.LocalVarPointer, topFrame.Method.ParameterCount); |
|
if (topFrame.Method.HasThis) |
|
arg--; |
|
if (arg->ObjectType == ObjectTypes.StackObjectReference) |
|
{ |
|
var addr = *(long*)&arg->Value; |
|
arg = (StackObject*)addr; |
|
} |
|
ILTypeInstance instance = arg->ObjectType != ObjectTypes.Null ? intepreter.Stack.ManagedStack[arg->Value] as ILTypeInstance : null; |
|
if (instance == null) |
|
return "null"; |
|
var fields = instance.Type.TypeDefinition.Fields; |
|
int idx = 0; |
|
StringBuilder sb = new StringBuilder(); |
|
for (int i = 0; i < fields.Count; i++) |
|
{ |
|
try |
|
{ |
|
var f = fields[i]; |
|
if (f.IsStatic) |
|
continue; |
|
var field = instance.Fields[idx]; |
|
var v = StackObject.ToObject(&field, intepreter.AppDomain, instance.ManagedObjects); |
|
if (v == null) |
|
v = "null"; |
|
string name = f.Name; |
|
sb.AppendFormat("{0} {1} = {2}", f.FieldType.Name, name, v); |
|
if ((idx % 3 == 0 && idx != 0) || idx == instance.Fields.Length - 1) |
|
sb.AppendLine(); |
|
else |
|
sb.Append(", "); |
|
idx++; |
|
} |
|
catch |
|
{ |
|
|
|
} |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
public unsafe string GetLocalVariableInfo(ILIntepreter intepreter) |
|
{ |
|
StackFrame topFrame = intepreter.Stack.Frames.Peek(); |
|
var m = topFrame.Method; |
|
StringBuilder sb = new StringBuilder(); |
|
for (int i = 0; i < m.LocalVariableCount; i++) |
|
{ |
|
try |
|
{ |
|
var lv = m.Definition.Body.Variables[i]; |
|
var val = Add(topFrame.LocalVarPointer, i); |
|
var v = StackObject.ToObject(val, intepreter.AppDomain, intepreter.Stack.ManagedStack); |
|
if (v == null) |
|
v = "null"; |
|
string vName = null; |
|
m.Definition.DebugInformation.TryGetName(lv, out vName); |
|
string name = string.IsNullOrEmpty(vName) ? "v" + lv.Index : vName; |
|
sb.AppendFormat("{0} {1} = {2}", lv.VariableType.Name, name, v); |
|
if ((i % 3 == 0 && i != 0) || i == m.LocalVariableCount - 1) |
|
sb.AppendLine(); |
|
else |
|
sb.Append(", "); |
|
} |
|
catch |
|
{ |
|
|
|
} |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
internal static Mono.Cecil.Cil.SequencePoint FindSequencePoint(Mono.Cecil.Cil.Instruction ins, IDictionary<Mono.Cecil.Cil.Instruction, Mono.Cecil.Cil.SequencePoint> seqMapping) |
|
{ |
|
Mono.Cecil.Cil.Instruction cur = ins; |
|
Mono.Cecil.Cil.SequencePoint sp; |
|
while (!seqMapping.TryGetValue(cur, out sp) && cur.Previous != null) |
|
cur = cur.Previous; |
|
|
|
return sp; |
|
} |
|
|
|
unsafe static StackObject* Add(StackObject* a, int b) |
|
{ |
|
return (StackObject*)((long)a + sizeof(StackObject) * b); |
|
} |
|
|
|
unsafe static StackObject* Minus(StackObject* a, int b) |
|
{ |
|
return (StackObject*)((long)a - sizeof(StackObject) * b); |
|
} |
|
|
|
internal void NotifyModuleLoaded(string moduleName) |
|
{ |
|
if (server != null && server.IsAttached) |
|
server.NotifyModuleLoaded(moduleName); |
|
} |
|
|
|
internal void SetBreakPoint(int methodHash, int bpHash, int startLine, bool enabled, BreakpointCondition breakpointCondition, UsingInfo[] usingInfos) |
|
{ |
|
lock (activeBreakpoints) |
|
{ |
|
LinkedList<BreakpointInfo> lst; |
|
if (!activeBreakpoints.TryGetValue(methodHash, out lst)) |
|
{ |
|
lst = new LinkedList<Debugger.BreakpointInfo>(); |
|
activeBreakpoints[methodHash] = lst; |
|
} |
|
|
|
BreakpointInfo bpInfo = new BreakpointInfo(); |
|
bpInfo.BreakpointHashCode = bpHash; |
|
bpInfo.MethodHashCode = methodHash; |
|
bpInfo.StartLine = startLine; |
|
bpInfo.Enabled = enabled; |
|
ParseBreakpointCondition(bpInfo, breakpointCondition); |
|
bpInfo.UsingInfos = usingInfos; |
|
|
|
lst.AddLast(bpInfo); |
|
breakpointMapping[bpHash] = bpInfo; |
|
} |
|
} |
|
|
|
private bool ParseBreakpointCondition(BreakpointInfo bpInfo, BreakpointCondition condition) |
|
{ |
|
#if ILRUNTIME_ENABLE_ROSYLN |
|
bpInfo.Condition = new BreakpointConditionDetails { Style = condition.Style, Expression = condition.Expression }; |
|
|
|
if (condition.Style == BreakpointConditionStyle.None) |
|
return true; |
|
|
|
var codeWithExpression = string.Format(breakpointParseCode, condition.Expression); |
|
var syntaxTree = CSharpSyntaxTree.ParseText(codeWithExpression); |
|
syntaxTree.TryGetRoot(out var root); |
|
var methodDeclarationSyntax = root.ChildNodes().First() as MethodDeclarationSyntax; |
|
var expressionStatementSyntax = methodDeclarationSyntax.Body.Statements[0] as ExpressionStatementSyntax; |
|
if (expressionStatementSyntax == null) |
|
{ |
|
bpInfo.Condition.ExpressionError = true; |
|
return false; |
|
} |
|
bpInfo.Condition.ExpressionSyntax = expressionStatementSyntax.Expression; |
|
#endif |
|
return true; |
|
} |
|
|
|
internal void SetBreakpointEnabled(int bpHash, bool enabled) |
|
{ |
|
lock (activeBreakpoints) |
|
{ |
|
BreakpointInfo bpInfo; |
|
if (breakpointMapping.TryGetValue(bpHash, out bpInfo)) |
|
bpInfo.Enabled = enabled; |
|
} |
|
} |
|
|
|
internal void SetBreakpointCondition(int bpHash, BreakpointConditionStyle style, string expression) |
|
{ |
|
lock (activeBreakpoints) |
|
{ |
|
BreakpointInfo bpInfo; |
|
if (breakpointMapping.TryGetValue(bpHash, out bpInfo)) |
|
ParseBreakpointCondition(bpInfo, new BreakpointCondition { Style = style, Expression = expression }); |
|
} |
|
} |
|
|
|
internal void DeleteBreakpoint(int bpHash) |
|
{ |
|
lock (activeBreakpoints) |
|
{ |
|
BreakpointInfo bpInfo; |
|
if (breakpointMapping.TryGetValue(bpHash, out bpInfo)) |
|
{ |
|
LinkedList<BreakpointInfo> lst; |
|
if (activeBreakpoints.TryGetValue(bpInfo.MethodHashCode, out lst)) |
|
{ |
|
lst.Remove(bpInfo); |
|
} |
|
breakpointMapping.Remove(bpHash); |
|
} |
|
} |
|
} |
|
|
|
internal void ExecuteThread(int threadHash) |
|
{ |
|
lock (AppDomain.FreeIntepreters) |
|
{ |
|
foreach (var i in AppDomain.Intepreters) |
|
{ |
|
//We should resume all threads on execute |
|
i.Value.ClearDebugState(); |
|
i.Value.Resume(); |
|
} |
|
} |
|
} |
|
|
|
internal unsafe void StepThread(int threadHash, StepTypes type) |
|
{ |
|
lock (AppDomain.FreeIntepreters) |
|
{ |
|
ILIntepreter intp; |
|
if (AppDomain.Intepreters.TryGetValue(threadHash, out intp)) |
|
{ |
|
intp.ClearDebugState(); |
|
intp.CurrentStepType = type; |
|
intp.LastStepInstructionIndex = intp.Stack.Frames.Count > 0 ? intp.Stack.Frames.Peek().Address.Value : 0; |
|
intp.LastStepFrameBase = intp.Stack.Frames.Count > 0 ? ResolveCurrentFrameBasePointer(intp) : (StackObject*)0; |
|
|
|
intp.Resume(); |
|
} |
|
} |
|
} |
|
|
|
unsafe StackObject* ResolveCurrentFrameBasePointer(ILIntepreter intp, ILMethod method = null, int ip = -1) |
|
{ |
|
StackObject* basePointer = intp.Stack.Frames.Peek().BasePointer; |
|
if (method == null) |
|
method = intp.Stack.Frames.Peek().Method; |
|
if (ip < 0) |
|
ip = intp.Stack.Frames.Peek().Address.Value; |
|
if (intp.Stack.Frames.Peek().IsRegister) |
|
{ |
|
basePointer = intp.Stack.Frames.Peek().LocalVarPointer; |
|
RegisterVMSymbol vmSymbol; |
|
if (method.RegisterVMSymbols.TryGetValue(ip, out vmSymbol)) |
|
{ |
|
var paramCnt = method.HasThis ? method.ParameterCount + 1 : method.ParameterCount; |
|
var frameBase = basePointer - paramCnt; |
|
int registerCnt = vmSymbol.Method.StackRegisterCount + vmSymbol.Method.LocalVariableCount; |
|
if (method.HasThis) |
|
frameBase--; |
|
var curParamCnt = vmSymbol.Method.HasThis ? vmSymbol.Method.ParameterCount + 1 : vmSymbol.Method.ParameterCount; |
|
|
|
if (vmSymbol.ParentSymbol != null) |
|
{ |
|
basePointer = frameBase + vmSymbol.ParentSymbol.BaseRegisterIndex; |
|
} |
|
else |
|
{ |
|
registerCnt -= vmSymbol.Method.StackRegisterCount; |
|
basePointer = frameBase; |
|
} |
|
basePointer = basePointer + curParamCnt + registerCnt; |
|
} |
|
} |
|
return basePointer; |
|
} |
|
|
|
unsafe internal void CheckShouldBreak(ILMethod method, ILIntepreter intp, int ip) |
|
{ |
|
if (server != null && server.IsAttached) |
|
{ |
|
RegisterVMSymbol vmSymbol; |
|
Mono.Cecil.Cil.Instruction ins = null; |
|
Mono.Cecil.MethodDefinition md = null; |
|
ILMethod m = method; |
|
if (intp.Stack.Frames.Peek().IsRegister) |
|
{ |
|
if (!method.IsRegisterVMSymbolFixed) |
|
method.FixRegisterVMSymbol(); |
|
if (method.RegisterVMSymbols.TryGetValue(ip, out vmSymbol)) |
|
{ |
|
ins = vmSymbol.Instruction; |
|
m = vmSymbol.Method; |
|
md = vmSymbol.Method.Definition; |
|
|
|
} |
|
} |
|
else |
|
{ |
|
md = method.Definition; |
|
ins = md.Body.Instructions[ip]; |
|
} |
|
StackObject* basePointer = ResolveCurrentFrameBasePointer(intp, method, ip); |
|
|
|
int methodHash = m.GetHashCode(); |
|
BreakpointInfo[] lst = null; |
|
|
|
lock (activeBreakpoints) |
|
{ |
|
LinkedList<BreakpointInfo> bps; |
|
if (activeBreakpoints.TryGetValue(methodHash, out bps)) |
|
lst = bps.ToArray(); |
|
} |
|
if (ins == null) |
|
return; |
|
if (lst != null) |
|
{ |
|
var sp = md.DebugInformation.GetSequencePoint(ins); |
|
if (sp != null) |
|
{ |
|
foreach (var i in lst) |
|
{ |
|
StackFrameInfo[] stackFrameInfos = null; |
|
string error = ""; |
|
if ((i.StartLine + 1) == sp.StartLine && i.Enabled && i.CheckCondition(this, intp, ref stackFrameInfos, ref error)) |
|
{ |
|
DoBreak(intp, i.BreakpointHashCode, false, stackFrameInfos, error); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (intp.CurrentStepType != StepTypes.None) |
|
{ |
|
var sp = md.DebugInformation.GetSequencePoint(ins); |
|
if (sp != null && IsSequenceValid(sp)) |
|
{ |
|
switch (intp.CurrentStepType) |
|
{ |
|
case StepTypes.Into: |
|
DoBreak(intp, 0, true); |
|
break; |
|
case StepTypes.Over: |
|
if (basePointer <= intp.LastStepFrameBase && ip != intp.LastStepInstructionIndex) |
|
{ |
|
DoBreak(intp, 0, true); |
|
} |
|
break; |
|
case StepTypes.Out: |
|
{ |
|
if (intp.Stack.Frames.Count > 0 && basePointer < intp.LastStepFrameBase) |
|
{ |
|
DoBreak(intp, 0, true); |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool IsSequenceValid(Mono.Cecil.Cil.SequencePoint sp) |
|
{ |
|
return sp.StartLine != sp.EndLine || sp.StartColumn != sp.EndColumn; |
|
} |
|
|
|
void DoBreak(ILIntepreter intp, int bpHash, bool isStep, StackFrameInfo[] stackFrameInfos = null, string error = null) |
|
{ |
|
var arr = AppDomain.Intepreters.ToArray(); |
|
KeyValuePair<int, StackFrameInfo[]>[] frames = new KeyValuePair<int, StackFrameInfo[]>[arr.Length]; |
|
frames[0] = new KeyValuePair<int, StackFrameInfo[]>(intp.GetHashCode(), stackFrameInfos == null ? GetStackFrameInfo(intp) : stackFrameInfos); |
|
int idx = 1; |
|
foreach (var j in arr) |
|
{ |
|
if (j.Value != intp) |
|
{ |
|
j.Value.ShouldBreak = true; |
|
try |
|
{ |
|
frames[idx++] = new KeyValuePair<int, Debugger.StackFrameInfo[]>(j.Value.GetHashCode(), GetStackFrameInfo(j.Value)); |
|
} |
|
catch |
|
{ |
|
frames[idx++] = new KeyValuePair<int, Debugger.StackFrameInfo[]>(j.Value.GetHashCode(), new StackFrameInfo[0]); |
|
} |
|
} |
|
} |
|
if (!isStep) |
|
server.SendSCBreakpointHit(intp.GetHashCode(), bpHash, frames, error); |
|
else |
|
server.SendSCStepComplete(intp.GetHashCode(), frames); |
|
//Breakpoint hit |
|
intp.Break(); |
|
} |
|
|
|
unsafe void InitializeStackFrameInfo(ILIntepreter intp, StackFrame f, List<StackFrameInfo> frameInfos) |
|
{ |
|
Mono.Cecil.Cil.Instruction ins = null; |
|
var m = f.Method; |
|
int argCnt = m.HasThis ? m.ParameterCount + 1 : m.ParameterCount; |
|
StackObject* frameBasePointer = Minus(f.LocalVarPointer, argCnt); |
|
if (f.Address != null) |
|
{ |
|
if (f.IsRegister) |
|
{ |
|
RegisterVMSymbol vmSymbol; |
|
if (m.RegisterVMSymbols.TryGetValue(f.Address.Value, out vmSymbol)) |
|
{ |
|
RegisterVMSymbolLink link = null; |
|
StackObject* basePointer; |
|
do |
|
{ |
|
if (link != null) |
|
{ |
|
vmSymbol = link.Value; |
|
} |
|
ins = vmSymbol.Instruction; |
|
m = vmSymbol.Method; |
|
if (vmSymbol.ParentSymbol != null) |
|
{ |
|
basePointer = Add(frameBasePointer, vmSymbol.ParentSymbol.BaseRegisterIndex); |
|
} |
|
else |
|
{ |
|
basePointer = frameBasePointer; |
|
} |
|
var info = CreateStackFrameInfo(m, ins); |
|
AddStackFrameInfoVariables(intp, info, m, basePointer); |
|
frameInfos.Add(info); |
|
link = vmSymbol.ParentSymbol; |
|
} |
|
while (link != null); |
|
} |
|
else |
|
{ |
|
var info = CreateStackFrameInfo(m, null); |
|
AddStackFrameInfoVariables(intp, info, m, frameBasePointer); |
|
frameInfos.Add(info); |
|
} |
|
} |
|
else |
|
{ |
|
ins = m.Definition.Body.Instructions[f.Address.Value]; |
|
var info = CreateStackFrameInfo(m, ins); |
|
AddStackFrameInfoVariables(intp, info, m, frameBasePointer); |
|
frameInfos.Add(info); |
|
} |
|
} |
|
else |
|
{ |
|
var info = CreateStackFrameInfo(m, null); |
|
AddStackFrameInfoVariables(intp, info, m, frameBasePointer); |
|
frameInfos.Add(info); |
|
} |
|
} |
|
|
|
StackFrameInfo CreateStackFrameInfo(ILMethod m, Mono.Cecil.Cil.Instruction ins) |
|
{ |
|
Mono.Cecil.MethodDefinition md = m.Definition; |
|
StackFrameInfo info = new StackFrameInfo(); |
|
info.MethodName = m.ToString(); |
|
if (ins != null) |
|
{ |
|
var seq = FindSequencePoint(ins, md.DebugInformation.GetSequencePointMapping()); |
|
if (seq != null) |
|
{ |
|
info.DocumentName = seq.Document.Url; |
|
info.StartLine = seq.StartLine - 1; |
|
info.StartColumn = seq.StartColumn - 1; |
|
info.EndLine = seq.EndLine - 1; |
|
info.EndColumn = seq.EndColumn - 1; |
|
} |
|
} |
|
return info; |
|
} |
|
|
|
unsafe void AddStackFrameInfoVariables(ILIntepreter intp, StackFrameInfo info, ILMethod m, StackObject* basePointer) |
|
{ |
|
int argumentCount = m.ParameterCount; |
|
if (m.HasThis) |
|
argumentCount++; |
|
info.ArgumentCount = argumentCount; |
|
info.LocalVariables = new VariableInfo[argumentCount + m.LocalVariableCount]; |
|
for (int i = 0; i < argumentCount; i++) |
|
{ |
|
int argIdx = m.HasThis ? i - 1 : i; |
|
var arg = basePointer; |
|
string name = null; |
|
object v = null; |
|
string typeName = null; |
|
var val = Add(arg, i); |
|
v = StackObject.ToObject(val, intp.AppDomain, intp.Stack.ManagedStack); |
|
CLR.TypeSystem.IType vType; |
|
if (argIdx >= 0) |
|
{ |
|
var lv = m.Definition.Parameters[argIdx]; |
|
name = string.IsNullOrEmpty(lv.Name) ? "arg" + lv.Index : lv.Name; |
|
typeName = lv.ParameterType.FullName; |
|
vType = m.Parameters[argIdx]; |
|
} |
|
else |
|
{ |
|
name = "this"; |
|
typeName = m.DeclearingType.FullName; |
|
vType = m.DeclearingType; |
|
} |
|
|
|
v = vType.TypeForCLR.CheckCLRTypes(v); |
|
VariableInfo vinfo = VariableInfo.FromObject(v); |
|
vinfo.Address = (long)val; |
|
vinfo.Name = name; |
|
vinfo.TypeName = typeName; |
|
vinfo.Expandable = GetValueExpandable(intp, val, intp.Stack.ManagedStack); |
|
vinfo.ValueObjType = vType.ReflectionType; |
|
|
|
info.LocalVariables[i] = vinfo; |
|
} |
|
for (int i = argumentCount; i < info.LocalVariables.Length; i++) |
|
{ |
|
var locIdx = i - argumentCount; |
|
var lv = m.Definition.Body.Variables[locIdx]; |
|
var val = Add(basePointer, argumentCount + locIdx); |
|
var v = StackObject.ToObject(val, intp.AppDomain, intp.Stack.ManagedStack); |
|
var type = intp.AppDomain.GetType(lv.VariableType, m.DeclearingType, m); |
|
string vName = null; |
|
m.Definition.DebugInformation.TryGetName(lv, out vName); |
|
string name = string.IsNullOrEmpty(vName) ? "v" + lv.Index : vName; |
|
v = type.TypeForCLR.CheckCLRTypes(v); |
|
VariableInfo vinfo = VariableInfo.FromObject(v); |
|
vinfo.Address = (long)val; |
|
vinfo.Name = name; |
|
vinfo.TypeName = lv.VariableType.FullName; |
|
vinfo.Expandable = GetValueExpandable(intp, val, intp.Stack.ManagedStack); |
|
vinfo.ValueObjType = type.ReflectionType; |
|
info.LocalVariables[i] = vinfo; |
|
} |
|
} |
|
|
|
internal unsafe StackFrameInfo[] GetStackFrameInfo(ILIntepreter intp) |
|
{ |
|
StackFrame[] frames = intp.Stack.Frames.ToArray(); |
|
List<StackFrameInfo> frameInfos = new List<StackFrameInfo>(); |
|
|
|
for (int j = 0; j < frames.Length; j++) |
|
{ |
|
InitializeStackFrameInfo(intp, frames[j], frameInfos); |
|
} |
|
return frameInfos.ToArray(); |
|
} |
|
|
|
internal unsafe VariableInfo[] EnumChildren(int threadHashCode, int frameIndex, VariableReference parent) |
|
{ |
|
ILIntepreter intepreter; |
|
if (AppDomain.Intepreters.TryGetValue(threadHashCode, out intepreter)) |
|
{ |
|
#if DEBUG && !NO_PROFILER |
|
if (domain.IsNotUnityMainThread()) |
|
{ |
|
lock (pendingEnuming) |
|
{ |
|
pendingEnuming.Enqueue(new Tuple<int, int, VariableReference>(threadHashCode, frameIndex, parent)); |
|
} |
|
return null; |
|
} |
|
#endif |
|
object obj; |
|
var info = ResolveVariable(threadHashCode, frameIndex, parent, out obj); |
|
if (obj != null) |
|
{ |
|
if (obj is Array) |
|
{ |
|
return EnumArray((Array)obj, intepreter); |
|
} |
|
else if (obj is IList) |
|
{ |
|
return EnumList((IList)obj, intepreter); |
|
} |
|
else if (obj is IDictionary) |
|
{ |
|
return EnumDictionary((IDictionary)obj, intepreter); |
|
} |
|
else if (obj is ILTypeInstance) |
|
{ |
|
return EnumObject(((ILTypeInstance)obj).CLRInstance, ((ILTypeInstance)obj).Type.ReflectionType); |
|
//return EnumILTypeInstance((ILTypeInstance)obj, intepreter); |
|
} |
|
else if (obj is ILRuntime.Runtime.Enviorment.CrossBindingAdaptorType) |
|
{ |
|
return EnumObject(obj, ((Enviorment.CrossBindingAdaptorType)obj).ILInstance.Type.ReflectionType); |
|
//return EnumILTypeInstance(((Enviorment.CrossBindingAdaptorType)obj).ILInstance, intepreter); |
|
} |
|
else |
|
{ |
|
return EnumCLRObject(obj, intepreter); |
|
} |
|
} |
|
else |
|
return new VariableInfo[] { VariableInfo.NullReferenceExeption }; |
|
} |
|
else |
|
return new VariableInfo[] { VariableInfo.NullReferenceExeption }; |
|
} |
|
|
|
VariableInfo[] EnumArray(Array arr, ILIntepreter intepreter) |
|
{ |
|
VariableInfo[] res = new VariableInfo[arr.Length]; |
|
|
|
for (int i = 0; i < arr.Length; i++) |
|
{ |
|
try |
|
{ |
|
var obj = arr.GetValue(i); |
|
|
|
VariableInfo info = VariableInfo.FromObject(obj, true); |
|
info.Name = string.Format("[{0}]", i); |
|
info.Offset = i; |
|
info.Type = VariableTypes.IndexAccess; |
|
res[i] = info; |
|
} |
|
catch (Exception ex) |
|
{ |
|
var info = VariableInfo.GetException(ex); |
|
info.Name = string.Format("[{0}]", i); |
|
res[i] = info; |
|
} |
|
} |
|
|
|
return res; |
|
} |
|
|
|
VariableInfo[] EnumList(IList lst, ILIntepreter intepreter) |
|
{ |
|
VariableInfo[] res = new VariableInfo[lst.Count]; |
|
|
|
for (int i = 0; i < lst.Count; i++) |
|
{ |
|
try |
|
{ |
|
var obj = lst[i]; |
|
|
|
VariableInfo info = VariableInfo.FromObject(obj, true); |
|
info.Name = string.Format("[{0}]", i); |
|
info.Offset = i; |
|
info.Type = VariableTypes.IndexAccess; |
|
|
|
res[i] = info; |
|
} |
|
catch (Exception ex) |
|
{ |
|
var info = VariableInfo.GetException(ex); |
|
info.Name = string.Format("[{0}]", i); |
|
res[i] = info; |
|
} |
|
} |
|
|
|
return res; |
|
} |
|
|
|
VariableInfo[] EnumDictionary(IDictionary lst, ILIntepreter intepreter) |
|
{ |
|
VariableInfo[] res = new VariableInfo[lst.Count]; |
|
|
|
var keys = GetArray(lst.Keys); |
|
var values = GetArray(lst.Values); |
|
for (int i = 0; i < lst.Count; i++) |
|
{ |
|
try |
|
{ |
|
var obj = values[i]; |
|
VariableInfo info = VariableInfo.FromObject(obj, true); |
|
info.Name = string.Format("[{0}]", i); |
|
info.Type = VariableTypes.IndexAccess; |
|
info.Offset = i; |
|
info.Value = string.Format("{0},{1}", SafeToString(keys[i]), SafeToString(values[i])); |
|
info.Expandable = true; |
|
res[i] = info; |
|
} |
|
catch (Exception ex) |
|
{ |
|
var info = VariableInfo.GetException(ex); |
|
info.Name = string.Format("[{0}]", i); |
|
res[i] = info; |
|
} |
|
} |
|
return res; |
|
} |
|
|
|
string SafeToString(object obj) |
|
{ |
|
if (obj != null) |
|
return obj.ToString(); |
|
else |
|
return "null"; |
|
} |
|
object[] GetArray(ICollection lst) |
|
{ |
|
object[] res = new object[lst.Count]; |
|
int idx = 0; |
|
foreach (var i in lst) |
|
{ |
|
res[idx++] = i; |
|
} |
|
return res; |
|
} |
|
|
|
VariableInfo[] EnumILTypeInstance(ILTypeInstance obj, ILIntepreter intepreter) |
|
{ |
|
return EnumObject(obj, obj.Type.ReflectionType); |
|
} |
|
|
|
VariableInfo[] EnumCLRObject(object obj, ILIntepreter intepreter) |
|
{ |
|
return EnumObject(obj, obj.GetType()); |
|
} |
|
|
|
VariableInfo[] EnumObject(object obj, Type t) |
|
{ |
|
List<VariableInfo> lst = new List<VariableInfo>(); |
|
foreach (var i in t.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)) |
|
{ |
|
try |
|
{ |
|
if (i.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false).Length > 0) |
|
continue; |
|
var val = i.GetValue(obj); |
|
VariableInfo info = VariableInfo.FromObject(val); |
|
info.Type = VariableTypes.FieldReference; |
|
info.TypeName = i.FieldType.FullName; |
|
info.Name = i.Name; |
|
info.Expandable = !i.FieldType.IsPrimitive && val != null; |
|
info.IsPrivate = i.IsPrivate; |
|
info.IsProtected = i.IsFamily; |
|
|
|
lst.Add(info); |
|
} |
|
catch (Exception ex) |
|
{ |
|
var info = VariableInfo.GetException(ex); |
|
info.Name = i.Name; |
|
lst.Add(info); |
|
} |
|
} |
|
|
|
foreach (var i in t.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)) |
|
{ |
|
try |
|
{ |
|
if (i.GetIndexParameters().Length > 0) |
|
continue; |
|
if (i.GetCustomAttributes(typeof(ObsoleteAttribute), true).Length > 0) |
|
continue; |
|
var val = i.GetValue(obj, null); |
|
VariableInfo info = VariableInfo.FromObject(val); |
|
info.Type = VariableTypes.PropertyReference; |
|
info.TypeName = i.PropertyType.FullName; |
|
info.Name = i.Name; |
|
info.Expandable = !i.PropertyType.IsPrimitive && val != null; |
|
info.IsPrivate = i.GetGetMethod(true).IsPrivate; |
|
info.IsProtected = i.GetGetMethod(true).IsFamily; |
|
|
|
lst.Add(info); |
|
} |
|
catch (Exception ex) |
|
{ |
|
var info = VariableInfo.GetException(ex); |
|
info.Name = i.Name; |
|
lst.Add(info); |
|
} |
|
} |
|
|
|
return lst.ToArray(); |
|
} |
|
|
|
internal unsafe VariableInfo ResolveIndexAccess(int threadHashCode, int frameIndex, VariableReference indexAccess, out object res) |
|
{ |
|
ILIntepreter intepreter; |
|
res = null; |
|
if (AppDomain.Intepreters.TryGetValue(threadHashCode, out intepreter)) |
|
{ |
|
#if DEBUG && !NO_PROFILER |
|
if (domain.IsNotUnityMainThread()) |
|
{ |
|
lock (pendingIndexing) |
|
{ |
|
pendingIndexing.Enqueue(new Tuple<int, int, VariableReference>(threadHashCode, frameIndex, indexAccess)); |
|
} |
|
res = null; |
|
return new VariableInfo() { Type = VariableTypes.Pending }; |
|
} |
|
#endif |
|
var body = indexAccess.Parent; |
|
var idx = indexAccess.Parameters[0]; |
|
object obj; |
|
var info = ResolveVariable(threadHashCode, frameIndex, body, out obj); |
|
var objType = info.ValueObjType; |
|
if (obj != null) |
|
{ |
|
object idxObj; |
|
info = ResolveVariable(threadHashCode, frameIndex, idx, out idxObj); |
|
if (obj is Array) |
|
{ |
|
res = ((Array)obj).GetValue((int)idxObj); |
|
info = VariableInfo.FromObject(res); |
|
info.Type = VariableTypes.IndexAccess; |
|
info.TypeName = obj.GetType().GetElementType().FullName; |
|
info.Expandable = res != null && !obj.GetType().GetElementType().IsPrimitive; |
|
info.ValueObjType = objType.GetElementType(); |
|
|
|
return info; |
|
} |
|
else |
|
{ |
|
//if(obj is ILTypeInstance) |
|
//{ |
|
// var m = ((ILTypeInstance)obj).Type.GetMethod("get_Item"); |
|
// if (m != null) |
|
// { |
|
// res = intepreter.AppDomain.Invoke(m, obj, idxObj); |
|
// info = VariableInfo.FromObject(res); |
|
// info.Type = VariableTypes.IndexAccess; |
|
// info.TypeName = m.ReturnType.FullName; |
|
// info.Expandable = res != null && !m.ReturnType.IsPrimitive; |
|
|
|
// return info; |
|
// } |
|
// else |
|
// return VariableInfo.NullReferenceExeption; |
|
//} |
|
//else |
|
//{ |
|
//if(obj is ILRuntime.Runtime.Enviorment.CrossBindingAdaptorType) |
|
//{ |
|
// throw new NotImplementedException(); |
|
//} |
|
//else |
|
//{ |
|
//if (obj is IDictionary && idxObj is int) |
|
//{ |
|
// IDictionary dic = (IDictionary)obj; |
|
// var keys = GetArray(dic.Keys); |
|
// if (keys[0].GetType() != typeof(int)) |
|
// { |
|
// int index = (int)idxObj; |
|
// var values = GetArray(dic.Values); |
|
// var t = typeof(KeyValuePair<,>).MakeGenericType(keys[index].GetType(), values[index].GetType()); |
|
// var ctor = t.GetConstructor(new Type[] { keys[index].GetType(), values[index].GetType() }); |
|
// res = ctor.Invoke(new object[] { keys[index], values[index] }); |
|
// info = VariableInfo.FromObject(res); |
|
// info.Type = VariableTypes.IndexAccess; |
|
// info.Offset = index; |
|
// info.TypeName = t.FullName; |
|
// info.Expandable = true; |
|
// info.ValueObjType = t; |
|
|
|
// return info; |
|
// } |
|
//} |
|
|
|
var pi = GetOverrideIndexer(objType, info.ValueObjType); |
|
//var pi = obj.GetType().GetProperty("Item"); |
|
if (pi != null) |
|
{ |
|
res = pi.GetValue(obj, new object[] { idxObj }); |
|
info = VariableInfo.FromObject(res); |
|
info.Type = VariableTypes.IndexAccess; |
|
info.TypeName = pi.PropertyType.FullName; |
|
info.Expandable = res != null && !pi.PropertyType.IsPrimitive; |
|
info.ValueObjType = pi.PropertyType; |
|
return info; |
|
} |
|
else |
|
return VariableInfo.GetError(string.Format("无法将带[] 的索引应用于“{0}”类型的表达式", objType.FullName)); |
|
//} |
|
//} |
|
} |
|
} |
|
else |
|
{ |
|
if (indexAccess.Conditional) |
|
{ |
|
if (objType.IsArray) |
|
{ |
|
info.ValueObjType = objType.GetElementType(); |
|
info.TypeName = info.ValueObjType.FullName; |
|
return info; |
|
} |
|
else |
|
{ |
|
object idxObj; |
|
info = ResolveVariable(threadHashCode, frameIndex, idx, out idxObj); |
|
var pi = GetOverrideIndexer(objType, info.ValueObjType); |
|
if (pi != null) |
|
{ |
|
info.ValueObjType = pi.PropertyType; |
|
info.TypeName = pi.PropertyType.FullName; |
|
return info; |
|
} |
|
else |
|
return VariableInfo.GetError(string.Format("无法将带[] 的索引应用于“{0}”类型的表达式", objType.FullName)); |
|
} |
|
} |
|
return VariableInfo.NullReferenceExeptionWithName(body.FullName); |
|
} |
|
} |
|
else |
|
return VariableInfo.NullReferenceExeption; |
|
} |
|
|
|
private PropertyInfo GetOverrideIndexer(Type bodyType, Type indexType) |
|
{ |
|
var checkedTypes = new Type[1] { indexType }; |
|
foreach (var property in bodyType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) |
|
{ |
|
var indexParameters = property.GetIndexParameters(); |
|
if (CheckParameters(indexParameters, checkedTypes, true)) |
|
return property; |
|
} |
|
return null; |
|
} |
|
|
|
internal void ResolvePendingRequests() |
|
{ |
|
lock (pendingReferences) |
|
{ |
|
while (pendingReferences.Count > 0) |
|
{ |
|
VariableInfo info; |
|
var r = pendingReferences.Dequeue(); |
|
try |
|
{ |
|
object res; |
|
info = ResolveVariable(r.Item1, r.Item2, r.Item3, out res); |
|
} |
|
catch (Exception ex) |
|
{ |
|
info = VariableInfo.GetException(ex); |
|
} |
|
server.SendSCResolveVariableResult(info); |
|
} |
|
} |
|
lock (pendingEnuming) |
|
{ |
|
while (pendingEnuming.Count > 0) |
|
{ |
|
VariableInfo[] info; |
|
var r = pendingEnuming.Dequeue(); |
|
try |
|
{ |
|
info = EnumChildren(r.Item1, r.Item2, r.Item3); |
|
} |
|
catch (Exception ex) |
|
{ |
|
info = new VariableInfo[] { VariableInfo.GetException(ex) }; |
|
} |
|
server.SendSCEnumChildrenResult(info); |
|
} |
|
} |
|
lock (pendingIndexing) |
|
{ |
|
while (pendingIndexing.Count > 0) |
|
{ |
|
VariableInfo info; |
|
var r = pendingIndexing.Dequeue(); |
|
try |
|
{ |
|
object res; |
|
info = ResolveIndexAccess(r.Item1, r.Item2, r.Item3, out res); |
|
} |
|
catch (Exception ex) |
|
{ |
|
info = VariableInfo.GetException(ex); |
|
} |
|
server.SendSCResolveVariableResult(info); |
|
} |
|
} |
|
} |
|
|
|
public unsafe static object GetThis(ILIntepreter intepreter, int frameIndex) |
|
{ |
|
ILMethod currentMethod; |
|
return GetThis(intepreter, frameIndex, out currentMethod); |
|
} |
|
|
|
public unsafe static object GetThis(ILIntepreter intepreter, int frameIndex, out ILMethod currentMethod) |
|
{ |
|
var frame = intepreter.Stack.Frames.ToArray()[frameIndex]; |
|
var m = frame.Method; |
|
currentMethod = m; |
|
if (m.HasThis) |
|
{ |
|
var addr = Minus(frame.LocalVarPointer, m.ParameterCount + 1); |
|
return StackObject.ToObject(addr, intepreter.AppDomain, intepreter.Stack.ManagedStack); |
|
} |
|
return null; |
|
} |
|
|
|
private void GetVariableReferenceParameters(int threadHashCode, int frameIndex, VariableReference[] parameters, out Type[] paramterTypes, out object[] paramterObjs) |
|
{ |
|
var paramterTypeList = new List<Type>(); |
|
var paramterObjsList = new List<object>(); |
|
foreach (var parameterReference in parameters) |
|
{ |
|
object parameterValue; |
|
var info = ResolveVariable(threadHashCode, frameIndex, parameterReference, out parameterValue); |
|
paramterTypeList.Add(info == null ? null : info.ValueObjType); |
|
paramterObjsList.Add(parameterValue); |
|
} |
|
paramterTypes = paramterTypeList.ToArray(); |
|
paramterObjs = paramterObjsList.ToArray(); |
|
} |
|
|
|
internal unsafe VariableInfo ResolveVariable(int threadHashCode, int frameIndex, VariableReference variable, out object res) |
|
{ |
|
ILIntepreter intepreter; |
|
res = null; |
|
if (AppDomain.Intepreters.TryGetValue(threadHashCode, out intepreter)) |
|
{ |
|
if (variable != null) |
|
{ |
|
#if DEBUG && !NO_PROFILER |
|
if (domain.IsNotUnityMainThread()) |
|
{ |
|
lock (pendingReferences) |
|
{ |
|
pendingReferences.Enqueue(new Tuple<int, int, VariableReference>(threadHashCode, frameIndex, variable)); |
|
} |
|
res = null; |
|
return new VariableInfo() { Type = VariableTypes.Pending }; |
|
} |
|
#endif |
|
switch (variable.Type) |
|
{ |
|
case VariableTypes.Normal: |
|
{ |
|
StackObject* ptr = (StackObject*)variable.Address; |
|
object obj = StackObject.ToObject(ptr, AppDomain, intepreter.Stack.ManagedStack); |
|
//if (obj != null) |
|
//{ |
|
if (variable.ValueType != null && obj != null) |
|
obj = variable.ValueType.UnWrapper().CheckCLRTypes(obj); |
|
res = obj; |
|
VariableInfo info = VariableInfo.FromObject(res); |
|
info.Address = variable.Address; |
|
info.Name = variable.Name; |
|
info.Type = VariableTypes.Normal; |
|
info.TypeName = variable.ValueType == null ? "" : variable.ValueType.FullName; |
|
info.ValueObjType = variable.ValueType; |
|
return info; |
|
//} |
|
//else |
|
//{ |
|
// return VariableInfo.Null; |
|
//} |
|
} |
|
case VariableTypes.FieldReference: |
|
case VariableTypes.PropertyReference: |
|
case VariableTypes.Invocation: |
|
{ |
|
object obj; |
|
Type[] paramterTypes = null; |
|
object[] paramterObjs = null; |
|
if (variable.Parent != null) |
|
{ |
|
var parentVariableInfo = ResolveVariable(threadHashCode, frameIndex, variable.Parent, out obj); |
|
if (parentVariableInfo.Type == VariableTypes.NotFound || parentVariableInfo.Type == VariableTypes.Error) |
|
return parentVariableInfo; |
|
if (variable.Type == VariableTypes.Invocation) |
|
{ |
|
GetVariableReferenceParameters(threadHashCode, frameIndex, variable.Parameters, out paramterTypes, out paramterObjs); |
|
} |
|
if (obj != null) |
|
{ |
|
return ResolveMember(obj, null, variable.Name, paramterTypes, paramterObjs, out res); |
|
} |
|
else |
|
{ |
|
if (parentVariableInfo.Type == VariableTypes.Class) |
|
{ |
|
return ResolveMember(null, parentVariableInfo.ValueObjType, variable.Name, paramterTypes, paramterObjs, out res); |
|
} |
|
if (variable.Conditional) |
|
{ |
|
parentVariableInfo.ValueObjType = GetMemberType(parentVariableInfo.ValueObjType, variable.Name, paramterTypes); // 返回的应是成员类型。例如a为复杂类型,b为a的string类型的成员,那么a?.b返回string |
|
if (parentVariableInfo.ValueObjType == null) |
|
return VariableInfo.GetCannotFind(variable.Name); |
|
return parentVariableInfo; |
|
} |
|
return VariableInfo.NullReferenceExeptionWithName(variable.Parent.FullName); |
|
} |
|
} |
|
else |
|
{ |
|
VariableInfo result = null; |
|
var v = GetThis(intepreter, frameIndex); |
|
if (v != null) |
|
{ |
|
if (variable.Type == VariableTypes.Invocation) |
|
{ |
|
GetVariableReferenceParameters(threadHashCode, frameIndex, variable.Parameters, out paramterTypes, out paramterObjs); |
|
} |
|
result = ResolveMember(v, null, variable.Name, paramterTypes, paramterObjs, out res); |
|
if (result.Type == VariableTypes.NotFound) |
|
{ |
|
ILTypeInstance ins = v as ILTypeInstance; |
|
if (ins != null) |
|
{ |
|
var ilType = ins.Type.ReflectionType; |
|
var fields = ilType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); |
|
foreach (var f in fields) |
|
{ |
|
if (f.Name.Contains("_this")) |
|
{ |
|
result = ResolveMember(f.GetValue(v), null, variable.Name, null, null, out res); |
|
if (result.Type != VariableTypes.NotFound) |
|
return result; |
|
} |
|
} |
|
} |
|
} |
|
//return result; |
|
} |
|
//else |
|
//{ |
|
// return VariableInfo.GetCannotFind(variable.Name); |
|
//} |
|
if (result != null && result.Type != VariableTypes.NotFound) |
|
return result; |
|
|
|
// variable.Name有可能表示一个类型 |
|
CLR.TypeSystem.IType targetIType = null; |
|
string typeFullName = null; |
|
if (UsingInfosContext != null) |
|
{ |
|
foreach (var usingInfo in UsingInfosContext) |
|
{ |
|
typeFullName = null; |
|
if (string.IsNullOrWhiteSpace(usingInfo.Alias)) // 用每个using+类型名称 进行匹配 |
|
typeFullName = usingInfo.Name + "." + variable.Name; |
|
else if (usingInfo.Alias == variable.Name) // 有别名的情况直接使用对应的名称 |
|
typeFullName = usingInfo.Name; |
|
if (typeFullName != null) |
|
{ |
|
var iType = AppDomain.GetType(typeFullName); |
|
if (iType != null) |
|
{ |
|
targetIType = iType; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
if (targetIType == null) |
|
{ |
|
typeFullName = variable.Name; |
|
targetIType = AppDomain.GetType(typeFullName); |
|
} |
|
if (targetIType != null) |
|
{ |
|
return new VariableInfo { Name = typeFullName, Type = VariableTypes.Class, ValueObjType = targetIType.ReflectionType }; |
|
} |
|
|
|
if (result != null) |
|
return result; |
|
return VariableInfo.GetCannotFind(variable.Name); |
|
} |
|
} |
|
case VariableTypes.IndexAccess: |
|
{ |
|
return ResolveIndexAccess(threadHashCode, frameIndex, variable, out res); |
|
} |
|
case VariableTypes.Value: |
|
{ |
|
res = variable.Value; |
|
VariableInfo info = VariableInfo.FromObject(res); |
|
info.ValueObjType = variable.ValueType; |
|
return info; |
|
} |
|
case VariableTypes.Integer: |
|
{ |
|
res = variable.Offset; |
|
return VariableInfo.GetInteger(variable.Offset); |
|
} |
|
case VariableTypes.String: |
|
{ |
|
res = variable.Name; |
|
return VariableInfo.GetString(variable.Name); |
|
} |
|
case VariableTypes.Boolean: |
|
{ |
|
if (variable.Offset == 1) |
|
{ |
|
res = true; |
|
return VariableInfo.True; |
|
} |
|
else |
|
{ |
|
res = false; |
|
return VariableInfo.False; |
|
} |
|
} |
|
case VariableTypes.Null: |
|
{ |
|
res = null; |
|
return VariableInfo.Null; |
|
} |
|
default: |
|
throw new NotImplementedException(); |
|
} |
|
} |
|
else |
|
{ |
|
return VariableInfo.NullReferenceExeption; |
|
} |
|
} |
|
else |
|
return VariableInfo.NullReferenceExeption; |
|
} |
|
|
|
Type GetMemberType(Type type, string name, Type[] parameterTypes) |
|
{ |
|
var bindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; |
|
if (parameterTypes == null) |
|
{ |
|
var fi = type.GetField(name, bindingFlag); |
|
if (fi != null) |
|
return fi.FieldType; |
|
var pi = type.GetProperty(name, bindingFlag); |
|
if (pi != null) |
|
return pi.PropertyType; |
|
} |
|
else |
|
{ |
|
var m = GetMethod(type, name, bindingFlag, false, parameterTypes); |
|
if (m != null) |
|
return m.ReturnType; |
|
} |
|
return null; |
|
} |
|
|
|
VariableInfo ResolveMember(object obj, Type objType, string name, Type[] parameterTypes, object[] parameters, out object res) |
|
{ |
|
res = null; |
|
Type type; |
|
if (obj == null) |
|
type = objType; |
|
else |
|
{ |
|
if (obj is ILTypeInstance) |
|
{ |
|
type = ((ILTypeInstance)obj).Type.ReflectionType; |
|
obj = ((ILTypeInstance)obj).CLRInstance; // 如果CLRInstance表示一个适配器,那么一定要用适配器实例作反射,否则得不到CLR成员的值 |
|
} |
|
else if (obj is Enviorment.CrossBindingAdaptorType) |
|
type = ((Enviorment.CrossBindingAdaptorType)obj).ILInstance.Type.ReflectionType; |
|
else |
|
type = obj.GetType(); |
|
} |
|
|
|
var bindingFlag = BindingFlags.Public | BindingFlags.NonPublic | (obj == null ? BindingFlags.Static : BindingFlags.Instance); |
|
if (parameterTypes == null) |
|
{ |
|
var fi = type.GetField(name, bindingFlag); |
|
if (fi != null) |
|
{ |
|
res = fi.GetValue(obj); |
|
VariableInfo info = VariableInfo.FromObject(res); |
|
|
|
info.Address = 0; |
|
info.Name = name; |
|
info.Type = VariableTypes.FieldReference; |
|
info.TypeName = fi.FieldType.FullName; |
|
info.IsPrivate = fi.IsPrivate; |
|
info.IsProtected = fi.IsFamily; |
|
info.Expandable = res != null && !fi.FieldType.IsPrimitive; |
|
info.ValueObjType = fi.FieldType; |
|
|
|
return info; |
|
} |
|
else |
|
{ |
|
var fields = type.GetFields(bindingFlag); |
|
string match = string.Format("<{0}>", name); |
|
foreach (var f in fields) |
|
{ |
|
if (f.Name.Contains(match)) |
|
{ |
|
res = f.GetValue(obj); |
|
VariableInfo info = VariableInfo.FromObject(res); |
|
|
|
info.Address = 0; |
|
info.Name = name; |
|
info.Type = VariableTypes.FieldReference; |
|
info.TypeName = f.FieldType.FullName; |
|
info.IsPrivate = f.IsPrivate; |
|
info.IsProtected = f.IsFamily; |
|
info.Expandable = res != null && !f.FieldType.IsPrimitive; |
|
info.ValueObjType = f.FieldType; |
|
|
|
return info; |
|
} |
|
} |
|
} |
|
|
|
var pi = type.GetProperty(name, bindingFlag); |
|
if (pi != null) |
|
{ |
|
res = pi.GetValue(obj, null); |
|
VariableInfo info = VariableInfo.FromObject(res); |
|
|
|
info.Address = 0; |
|
info.Name = name; |
|
info.Type = VariableTypes.PropertyReference; |
|
info.TypeName = pi.PropertyType.FullName; |
|
info.IsPrivate = pi.GetGetMethod(true).IsPrivate; |
|
info.IsProtected = pi.GetGetMethod(true).IsFamily; |
|
info.Expandable = res != null && !pi.PropertyType.IsPrimitive; |
|
info.ValueObjType = pi.PropertyType; |
|
return info; |
|
} |
|
} |
|
else |
|
{ |
|
var method = GetMethod(type, name, bindingFlag, false, parameterTypes); |
|
if (method != null) |
|
{ |
|
res = method.Invoke(obj, parameters); |
|
VariableInfo info = VariableInfo.FromObject(res); |
|
|
|
info.Address = 0; |
|
info.Name = name; |
|
info.Type = VariableTypes.Invocation; |
|
info.TypeName = method.ReturnType.FullName; |
|
info.IsPrivate = method.IsPrivate; |
|
info.IsProtected = method.IsFamily; |
|
info.Expandable = false; |
|
info.ValueObjType = method.ReturnType; |
|
return info; |
|
} |
|
} |
|
|
|
return VariableInfo.GetCannotFind(name); |
|
} |
|
|
|
public static MethodInfo GetMethod(Type searchType, string methodName, BindingFlags bindingFlags, bool exactlyParametersCount, params Type[] parameterTypes) |
|
{ |
|
if (searchType == null) |
|
return null; |
|
return GetMethod(searchType, methodName, bindingFlags, (methodInfo) => |
|
{ |
|
var parameterInfos = methodInfo.GetParameters(); |
|
if (parameterInfos.Length < parameterTypes.Length) |
|
return false; |
|
return CheckParameters(parameterInfos, parameterTypes, exactlyParametersCount); |
|
}); |
|
} |
|
|
|
public static MethodInfo GetMethod(Type searchType, string methodName, BindingFlags bindingFlags, Func<MethodInfo, bool> parameterPredicate) |
|
{ |
|
var methods = searchType.GetMethods(bindingFlags); |
|
foreach (var method in methods) |
|
{ |
|
if (method.Name != methodName) |
|
continue; |
|
if (parameterPredicate(method)) |
|
return method; |
|
} |
|
return null; |
|
} |
|
|
|
private static bool CheckParameters(ParameterInfo[] parameters, Type[] checkTypes, bool exactlyParametersCount) |
|
{ |
|
if (exactlyParametersCount && parameters.Length != checkTypes.Length) |
|
return false; |
|
|
|
for (int i = 0; i < parameters.Length; ++i) |
|
{ |
|
if (i < checkTypes.Length) |
|
{ |
|
if (checkTypes[i] == null) |
|
{ |
|
if (parameters[i].ParameterType.IsValueType) // null只能匹配引用类型 |
|
return false; |
|
} |
|
else if (!parameters[i].ParameterType.IsAssignableFrom(checkTypes[i])) |
|
return false; |
|
} |
|
else if (!parameters[i].HasDefaultValue) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
unsafe bool GetValueExpandable(ILIntepreter intp, StackObject* esp, IList<object> mStack) |
|
{ |
|
if (esp->ObjectType < ObjectTypes.ValueTypeObjectReference) |
|
return false; |
|
else |
|
{ |
|
object obj; |
|
if(esp->ObjectType == ObjectTypes.ValueTypeObjectReference) |
|
{ |
|
obj = StackObject.ToObject(esp, intp.AppDomain, mStack); |
|
} |
|
else |
|
obj = mStack[esp->Value]; |
|
if (obj == null) |
|
return false; |
|
if (obj is ILTypeInstance) |
|
return true; |
|
else if (obj.GetType().IsPrimitive) |
|
return false; |
|
else |
|
return true; |
|
|
|
} |
|
} |
|
|
|
internal void ThreadStarted(ILIntepreter intp) |
|
{ |
|
if (server != null && server.IsAttached) |
|
{ |
|
server.SendSCThreadStarted(intp.GetHashCode()); |
|
} |
|
} |
|
|
|
internal void ThreadEnded(ILIntepreter intp) |
|
{ |
|
if (server != null && server.IsAttached) |
|
{ |
|
server.SendSCThreadEnded(intp.GetHashCode()); |
|
} |
|
} |
|
|
|
internal void Detach() |
|
{ |
|
activeBreakpoints.Clear(); |
|
breakpointMapping.Clear(); |
|
pendingEnuming.Clear(); |
|
pendingReferences.Clear(); |
|
pendingIndexing.Clear(); |
|
foreach (var j in AppDomain.Intepreters) |
|
{ |
|
j.Value.ClearDebugState(); |
|
j.Value.Resume(); |
|
} |
|
} |
|
|
|
internal unsafe void DumpStack(StackObject* esp, RuntimeStack stack) |
|
{ |
|
var start = stack.StackBase; |
|
var end = esp + 10; |
|
var frames = stack.Frames; |
|
var mStack = stack.ManagedStack; |
|
var valuePointerEnd = stack.ValueTypeStackPointer; |
|
StringBuilder final = new StringBuilder(); |
|
HashSet<long> leakVObj = new HashSet<long>(); |
|
for (var i = stack.ValueTypeStackBase; i > stack.ValueTypeStackPointer;) |
|
{ |
|
leakVObj.Add((long)i); |
|
i = Minus(i, i->ValueLow + 1); |
|
} |
|
for (var i = start; i <= end; i++) |
|
{ |
|
StringBuilder sb = new StringBuilder(); |
|
ILMethod localMethod = null, baseMethod = null; |
|
bool isLocal = false; |
|
bool isBase = false; |
|
int localIdx = 0; |
|
if (i == esp) |
|
sb.Append("->"); |
|
foreach (var j in frames) |
|
{ |
|
if (i >= j.LocalVarPointer && i < j.BasePointer) |
|
{ |
|
isLocal = true; |
|
localIdx = (int)(i - j.LocalVarPointer); |
|
localMethod = j.Method; |
|
} |
|
else if (i == j.BasePointer) |
|
{ |
|
isBase = true; |
|
baseMethod = j.Method; |
|
} |
|
} |
|
sb.Append(string.Format("(0x{0:X8}) Type:{1} ", (long)i, i->ObjectType)); |
|
try |
|
{ |
|
GetStackObjectText(sb, i, mStack, valuePointerEnd); |
|
} |
|
catch |
|
{ |
|
sb.Append(" Cannot Fetch Object Info"); |
|
} |
|
if (i < esp) |
|
{ |
|
if (i->ObjectType == ObjectTypes.ValueTypeObjectReference) |
|
VisitValueTypeReference(ILIntepreter.ResolveReference(i), leakVObj); |
|
} |
|
if (isLocal) |
|
{ |
|
sb.Append(string.Format("|Loc:{0}", localIdx)); |
|
if (localIdx == 0) |
|
{ |
|
sb.Append(" Method:"); |
|
sb.Append(localMethod.ToString()); |
|
} |
|
} |
|
if (isBase) |
|
{ |
|
sb.Append("|Base"); |
|
sb.Append(" Method:"); |
|
sb.Append(baseMethod.ToString()); |
|
} |
|
|
|
final.AppendLine(sb.ToString()); |
|
} |
|
|
|
for (var i = stack.ValueTypeStackBase; i > stack.ValueTypeStackPointer;) |
|
{ |
|
var vt = domain.GetTypeByIndex(i->Value); |
|
var cnt = i->ValueLow; |
|
bool leak = leakVObj.Contains((long)i); |
|
final.AppendLine("----------------------------------------------"); |
|
final.AppendLine(string.Format("{2}(0x{0:X8}){1}", (long)i, vt, leak ? "*" : "")); |
|
for (int j = 0; j < cnt; j++) |
|
{ |
|
StringBuilder sb = new StringBuilder(); |
|
var ptr = Minus(i, j + 1); |
|
sb.Append(string.Format("(0x{0:X8}) Type:{1} ", (long)ptr, ptr->ObjectType)); |
|
GetStackObjectText(sb, ptr, mStack, valuePointerEnd); |
|
final.AppendLine(sb.ToString()); |
|
} |
|
i = Minus(i, i->ValueLow + 1); |
|
} |
|
final.AppendLine("Managed Objects:"); |
|
for (int i = 0; i < mStack.Count; i++) |
|
{ |
|
final.AppendLine(string.Format("({0}){1}", i, mStack[i])); |
|
} |
|
#if !UNITY_5 && !UNITY_2017_1_OR_NEWER && !UNITY_4 |
|
System.Diagnostics.Debug.Print(final.ToString()); |
|
#else |
|
UnityEngine.Debug.LogWarning(final.ToString()); |
|
#endif |
|
} |
|
|
|
unsafe void GetStackObjectText(StringBuilder sb, StackObject* esp, IList<object> mStack, StackObject* valueTypeEnd) |
|
{ |
|
string text = "null"; |
|
switch (esp->ObjectType) |
|
{ |
|
case ObjectTypes.StackObjectReference: |
|
{ |
|
sb.Append(string.Format("Value:0x{0:X8}", (long)ILIntepreter.ResolveReference(esp))); |
|
} |
|
break; |
|
case ObjectTypes.ValueTypeObjectReference: |
|
{ |
|
object obj = null; |
|
var dst = ILIntepreter.ResolveReference(esp); |
|
try |
|
{ |
|
if (dst > valueTypeEnd) |
|
obj = StackObject.ToObject(esp, domain, mStack); |
|
if (obj != null) |
|
text = obj.ToString(); |
|
} |
|
catch |
|
{ |
|
text = "Invalid Object"; |
|
} |
|
text += string.Format("({0})", domain.GetTypeByIndex(dst->Value)); |
|
} |
|
sb.Append(string.Format("Value:0x{0:X8} Text:{1} ", (long)ILIntepreter.ResolveReference(esp), text)); |
|
break; |
|
default: |
|
{ |
|
if (esp->ObjectType >= ObjectTypes.Null && esp->ObjectType <= ObjectTypes.ArrayReference) |
|
{ |
|
if (esp->ObjectType < ObjectTypes.Object || esp->Value < mStack.Count) |
|
{ |
|
var obj = StackObject.ToObject(esp, domain, mStack); |
|
if (obj != null) |
|
text = obj.ToString(); |
|
} |
|
} |
|
|
|
sb.Append(string.Format("Value:{0} ValueLow:{1} Text:{2} ", esp->Value, esp->ValueLow, text)); |
|
} |
|
break; |
|
|
|
} |
|
} |
|
|
|
unsafe void VisitValueTypeReference(StackObject* esp, HashSet<long> leak) |
|
{ |
|
leak.Remove((long)esp); |
|
for (int i = 0; i < esp->ValueLow; i++) |
|
{ |
|
var ptr = Minus(esp, i + 1); |
|
if (ptr->ObjectType == ObjectTypes.ValueTypeObjectReference) |
|
{ |
|
VisitValueTypeReference(ILIntepreter.ResolveReference(ptr), leak); |
|
} |
|
} |
|
} |
|
} |
|
} |