// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Debugger.MetaData; using Debugger.Interop.CorDebug; namespace Debugger { public enum EvalState { Evaluating, EvaluatedSuccessfully, EvaluatedException, EvaluatedNoResult, EvaluatedTimeOut, }; /// /// This class holds information about function evaluation. /// public class Eval: DebuggerObject { delegate void EvalStarter(Eval eval); AppDomain appDomain; Process process; string description; ICorDebugEval corEval; Thread thread; Value result; EvalState state; public AppDomain AppDomain { get { return appDomain; } } public Process Process { get { return process; } } public string Description { get { return description; } } public ICorDebugEval CorEval { get { return corEval; } } public ICorDebugEval2 CorEval2 { get { return (ICorDebugEval2)corEval; } } /// Evaluating... public Value Result { get { switch(this.State) { case EvalState.Evaluating: throw new GetValueException("Evaluating..."); case EvalState.EvaluatedSuccessfully: return result; case EvalState.EvaluatedException: return result; case EvalState.EvaluatedNoResult: return null; case EvalState.EvaluatedTimeOut: throw new GetValueException("Timeout"); default: throw new DebuggerException("Unknown state"); } } } public EvalState State { get { return state; } } public bool Evaluated { get { return state == EvalState.EvaluatedSuccessfully || state == EvalState.EvaluatedException || state == EvalState.EvaluatedNoResult || state == EvalState.EvaluatedTimeOut; } } Eval(AppDomain appDomain, string description, EvalStarter evalStarter) { this.appDomain = appDomain; this.process = appDomain.Process; this.description = description; this.state = EvalState.Evaluating; this.thread = GetEvaluationThread(appDomain); this.corEval = thread.CorThread.CreateEval(); try { evalStarter(this); } catch (COMException e) { if ((uint)e.ErrorCode == 0x80131C26) { throw new GetValueException("Can not evaluate in optimized code"); } else if ((uint)e.ErrorCode == 0x80131C28) { throw new GetValueException("Object is in wrong AppDomain"); } else if ((uint)e.ErrorCode == 0x8013130A) { // Happens on getting of Sytem.Threading.Thread.ManagedThreadId; See SD2-1116 throw new GetValueException("Function does not have IL code"); } else if ((uint)e.ErrorCode == 0x80131C23) { // The operation failed because it is a GC unsafe point. (Exception from HRESULT: 0x80131C23) // This can probably happen when we break and the thread is in native code throw new GetValueException("Thread is in GC unsafe point"); } else if ((uint)e.ErrorCode == 0x80131C22) { // The operation is illegal because of a stack overflow. throw new GetValueException("Can not evaluate after stack overflow"); } else if ((uint)e.ErrorCode == 0x80131313) { // Func eval cannot work. Bad starting point. // Reproduction circumstancess are unknown throw new GetValueException("Func eval cannot work. Bad starting point."); } else { #if DEBUG throw; // Expose for more diagnostics #else throw new GetValueException(e.Message); #endif } } appDomain.Process.ActiveEvals.Add(this); if (appDomain.Process.Options.SuspendOtherThreads) { appDomain.Process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] { thread }, CorDebugThreadState.THREAD_SUSPEND); } else { appDomain.Process.AsyncContinue(DebuggeeStateAction.Keep, this.Process.UnsuspendedThreads, CorDebugThreadState.THREAD_RUN); } } static Thread GetEvaluationThread(AppDomain appDomain) { appDomain.Process.AssertPaused(); Thread st = appDomain.Process.SelectedThread; if (st != null && !st.Suspended && !st.IsInNativeCode && st.IsAtSafePoint && st.CorThread.GetAppDomain().GetID() == appDomain.ID) { return st; } foreach(Thread t in appDomain.Process.Threads) { if (!t.Suspended && !t.IsInNativeCode && t.IsAtSafePoint && t.CorThread.GetAppDomain().GetID() == appDomain.ID) { return t; } } throw new GetValueException("No suitable thread for evaluation"); } internal bool IsCorEval(ICorDebugEval corEval) { return this.corEval == corEval; } /// Evaluation can not be stopped /// Process exited Value WaitForResult() { // Note that aborting is not supported for suspended threads try { process.WaitForPause(TimeSpan.FromMilliseconds(500)); if (!Evaluated) { process.TraceMessage("Aborting eval: " + Description); this.CorEval.Abort(); process.WaitForPause(TimeSpan.FromMilliseconds(2500)); if (!Evaluated) { process.TraceMessage("Rude aborting eval: " + Description); this.CorEval2.RudeAbort(); process.WaitForPause(TimeSpan.FromMilliseconds(5000)); if (!Evaluated) { throw new DebuggerException("Evaluation can not be stopped"); } } // Note that this sets Evaluated to true state = EvalState.EvaluatedTimeOut; } process.AssertPaused(); return this.Result; } catch (ProcessExitedException) { throw new GetValueException("Process exited"); } } internal void NotifyEvaluationComplete(bool successful) { // Eval result should be ICorDebugHandleValue so it should survive Continue() if (state == EvalState.EvaluatedTimeOut) { return; } if (corEval.GetResult() == null) { state = EvalState.EvaluatedNoResult; } else { if (successful) { state = EvalState.EvaluatedSuccessfully; } else { state = EvalState.EvaluatedException; } result = new Value(AppDomain, corEval.GetResult()); } } /// Synchronously calls a function and returns its return value public static Value InvokeMethod(DebugMethodInfo method, Value thisValue, Value[] args) { if (method.BackingField != null) { method.Process.TraceMessage("Using backing field for " + method.FullName); return Value.GetMemberValue(thisValue, method.BackingField, args); } return AsyncInvokeMethod(method, thisValue, args).WaitForResult(); } public static Eval AsyncInvokeMethod(DebugMethodInfo method, Value thisValue, Value[] args) { return new Eval( method.AppDomain, "Function call: " + method.FullName, delegate(Eval eval) { MethodInvokeStarter(eval, method, thisValue, args); } ); } /// GetValueException. static void MethodInvokeStarter(Eval eval, DebugMethodInfo method, Value thisValue, Value[] args) { List corArgs = new List(); args = args ?? new Value[0]; if (args.Length != method.ParameterCount) { throw new GetValueException("Invalid parameter count"); } if (!method.IsStatic) { if (thisValue == null) throw new GetValueException("'this' is null"); if (thisValue.IsNull) throw new GetValueException("Null reference"); // if (!(thisValue.IsObject)) // eg Can evaluate on array if (!method.DeclaringType.IsInstanceOfType(thisValue)) { throw new GetValueException( "Can not evaluate because the object is not of proper type. " + "Expected: " + method.DeclaringType.FullName + " Seen: " + thisValue.Type.FullName ); } corArgs.Add(thisValue.CorValue); } for(int i = 0; i < args.Length; i++) { Value arg = args[i]; DebugType paramType = (DebugType)method.GetParameters()[i].ParameterType; if (!arg.Type.CanImplicitelyConvertTo(paramType)) throw new GetValueException("Inncorrect parameter type"); // Implicitely convert to correct primitve type if (paramType.IsPrimitive && args[i].Type != paramType) { object oldPrimVal = arg.PrimitiveValue; object newPrimVal = Convert.ChangeType(oldPrimVal, paramType.PrimitiveType); arg = CreateValue(method.AppDomain, newPrimVal); } // It is importatnt to pass the parameted in the correct form (boxed/unboxed) if (paramType.IsValueType) { corArgs.Add(arg.CorGenericValue); } else { if (args[i].Type.IsValueType) { corArgs.Add(arg.Box().CorValue); } else { corArgs.Add(arg.CorValue); } } } ICorDebugType[] genericArgs = ((DebugType)method.DeclaringType).GenericArgumentsAsCorDebugType; eval.CorEval2.CallParameterizedFunction( method.CorFunction, (uint)genericArgs.Length, genericArgs, (uint)corArgs.Count, corArgs.ToArray() ); } public static Value CreateValue(AppDomain appDomain, object value) { if (value == null) { ICorDebugClass corClass = appDomain.ObjectType.CorType.GetClass(); Thread thread = GetEvaluationThread(appDomain); ICorDebugEval corEval = thread.CorThread.CreateEval(); ICorDebugValue corValue = corEval.CreateValue((uint)CorElementType.CLASS, corClass); return new Value(appDomain, corValue); } else if (value is string) { return Eval.NewString(appDomain, (string)value); } else { if (!value.GetType().IsPrimitive) throw new DebuggerException("Value must be primitve type. Seen " + value.GetType()); Value val = Eval.NewObjectNoConstructor(DebugType.CreateFromType(appDomain.Mscorlib, value.GetType())); val.PrimitiveValue = value; return val; } } /* // The following function create values only for the purpuse of evalutaion // They actually do not allocate memory on the managed heap // The advantage is that it does not continue the process /// Can not create string this way public static Value CreateValue(Process process, object value) { if (value is string) throw new DebuggerException("Can not create string this way"); CorElementType corElemType; ICorDebugClass corClass = null; if (value != null) { corElemType = DebugType.TypeNameToCorElementType(value.GetType().FullName); } else { corElemType = CorElementType.CLASS; corClass = DebugType.Create(process, null, typeof(object).FullName).CorType.Class; } ICorDebugEval corEval = CreateCorEval(process); ICorDebugValue corValue = corEval.CreateValue((uint)corElemType, corClass); Value v = new Value(process, new Expressions.PrimitiveExpression(value), corValue); if (value != null) { v.PrimitiveValue = value; } return v; } */ #region Convenience methods public static Value NewString(AppDomain appDomain, string textToCreate) { return AsyncNewString(appDomain, textToCreate).WaitForResult(); } #endregion public static Eval AsyncNewString(AppDomain appDomain, string textToCreate) { return new Eval( appDomain, "New string: " + textToCreate, delegate(Eval eval) { eval.CorEval2.NewStringWithLength(textToCreate, (uint)textToCreate.Length); } ); } #region Convenience methods public static Value NewArray(DebugType type, uint length, uint? lowerBound) { return AsyncNewArray(type, length, lowerBound).WaitForResult(); } #endregion public static Eval AsyncNewArray(DebugType type, uint length, uint? lowerBound) { lowerBound = lowerBound ?? 0; return new Eval( type.AppDomain, "New array: " + type + "[" + length + "]", delegate(Eval eval) { // Multi-dimensional arrays not supported in .NET 2.0 eval.CorEval2.NewParameterizedArray(type.CorType, 1, new uint[] { length }, new uint[] { lowerBound.Value }); } ); } #region Convenience methods public static Value NewObject(DebugMethodInfo constructor, Value[] constructorArguments) { return AsyncNewObject(constructor, constructorArguments).WaitForResult(); } #endregion public static Eval AsyncNewObject(DebugMethodInfo constructor, Value[] constructorArguments) { ICorDebugValue[] constructorArgsCorDebug = ValuesAsCorDebug(constructorArguments); return new Eval( constructor.AppDomain, "New object: " + constructor.FullName, delegate(Eval eval) { eval.CorEval2.NewParameterizedObject( constructor.CorFunction, (uint)constructor.DeclaringType.GetGenericArguments().Length, ((DebugType)constructor.DeclaringType).GenericArgumentsAsCorDebugType, (uint)constructorArgsCorDebug.Length, constructorArgsCorDebug); } ); } #region Convenience methods public static Value NewObjectNoConstructor(DebugType debugType) { return AsyncNewObjectNoConstructor(debugType).WaitForResult(); } #endregion public static Eval AsyncNewObjectNoConstructor(DebugType debugType) { return new Eval( debugType.AppDomain, "New object: " + debugType.FullName, delegate(Eval eval) { eval.CorEval2.NewParameterizedObjectNoConstructor(debugType.CorType.GetClass(), (uint)debugType.GetGenericArguments().Length, debugType.GenericArgumentsAsCorDebugType); } ); } static ICorDebugValue[] ValuesAsCorDebug(Value[] values) { ICorDebugValue[] valuesAsCorDebug = new ICorDebugValue[values.Length]; for(int i = 0; i < values.Length; i++) { valuesAsCorDebug[i] = values[i].CorValue; } return valuesAsCorDebug; } } }