/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet.Evaluation; using dnSpy.Contracts.Debugger.Engine.Evaluation; using dnSpy.Contracts.Debugger.Evaluation; using dnSpy.Debugger.DotNet.Interpreter; using dnSpy.Debugger.DotNet.Metadata; namespace dnSpy.Debugger.DotNet.Evaluation.Engine.Interpreter { abstract class DbgDotNetILInterpreter { public abstract DbgDotNetILInterpreterState CreateState(byte[] assembly); public DbgDotNetValueResult Execute(DbgEvaluationInfo evalInfo, DbgDotNetILInterpreterState state, string typeName, string methodName, DbgEvaluationOptions options, out DmdType expectedType) { var frameMethod = evalInfo.Runtime.GetDotNetRuntime().GetFrameMethod(evalInfo) ?? throw new InvalidOperationException(); var genericTypeArguments = frameMethod.ReflectedType!.GetGenericArguments(); var genericMethodArguments = frameMethod.GetGenericArguments(); return Execute(evalInfo, genericTypeArguments, genericMethodArguments, null, null, state, typeName, methodName, options, out expectedType); } public abstract DbgDotNetValueResult Execute(DbgEvaluationInfo evalInfo, IList genericTypeArguments, IList genericMethodArguments, VariablesProvider? argumentsProvider, VariablesProvider? localsProvider, DbgDotNetILInterpreterState state, string typeName, string methodName, DbgEvaluationOptions options, out DmdType expectedType); } abstract class DbgDotNetILInterpreterState { } [Export(typeof(DbgDotNetILInterpreter))] sealed class DbgDotNetILInterpreterImpl : DbgDotNetILInterpreter { sealed class DbgDotNetILInterpreterStateImpl : DbgDotNetILInterpreterState { public ILVM ILVM { get; } readonly List<(int appDomainId, string typeName, string methodName, IList genericTypeArguments, IList genericMethodArguments, MethodInfoState methodState)> methodStates; readonly List<(int appDomainId, DmdAssembly assembly)> assemblies; readonly byte[] assemblyBytes; public DbgDotNetILInterpreterStateImpl(byte[] assemblyBytes) { ILVM = ILVMFactory.Create(); methodStates = new List<(int, string, string, IList, IList, MethodInfoState)>(); assemblies = new List<(int, DmdAssembly)>(); this.assemblyBytes = assemblyBytes ?? throw new ArgumentNullException(nameof(assemblyBytes)); } public MethodInfoState GetOrCreateMethodInfoState(DbgAppDomain appDomain, string typeName, string methodName, IList genericTypeArguments, IList genericMethodArguments) { int appDomainId = appDomain.Id; foreach (var info in methodStates) { if (methodName == info.methodName && typeName == info.typeName && info.appDomainId == appDomainId && Equals(info.genericTypeArguments, genericTypeArguments) && Equals(info.genericMethodArguments, genericMethodArguments)) return info.methodState; } var methodState = new MethodInfoState(); methodStates.Add((appDomain.Id, typeName, methodName, genericTypeArguments, genericMethodArguments, methodState)); return methodState; } bool Equals(IList a, IList b) { if (a == b) return true; if (a.Count != b.Count) return false; for (int i = 0; i < a.Count; i++) { if (a[i] != b[i]) return false; } return true; } public DmdAssembly GetOrCreateReflectionAssembly(DbgAppDomain appDomain) { foreach (var info in assemblies) { if (info.appDomainId == appDomain.Id) return info.assembly; } var reflectionAppDomain = appDomain.GetReflectionAppDomain(); if (reflectionAppDomain is null) throw new InvalidOperationException(); const bool isInMemory = true; const bool isDynamic = false; var fullyQualifiedName = DmdModule.GetFullyQualifiedName(isInMemory, isDynamic, fullyQualifiedName: null); Func getMetadata = () => new DmdLazyMetadataBytesArray(assemblyBytes, isFileLayout: true); var assembly = reflectionAppDomain.CreateSyntheticAssembly(getMetadata, isInMemory, isDynamic, fullyQualifiedName, string.Empty, null); assemblies.Add((appDomain.Id, assembly)); return assembly; } } readonly DebuggerRuntimeFactory debuggerRuntimeFactory; [ImportingConstructor] DbgDotNetILInterpreterImpl(DebuggerRuntimeFactory debuggerRuntimeFactory) => this.debuggerRuntimeFactory = debuggerRuntimeFactory; sealed class MethodInfoState { public ILVMExecuteState? ILVMExecuteState { get; set; } public DmdType? ExpectedType { get; set; } public DmdMethodBody? RealMethodBody { get; set; } } public override DbgDotNetILInterpreterState CreateState(byte[] assembly) { if (assembly is null) throw new ArgumentNullException(nameof(assembly)); return new DbgDotNetILInterpreterStateImpl(assembly); } public override DbgDotNetValueResult Execute(DbgEvaluationInfo evalInfo, IList genericTypeArguments, IList genericMethodArguments, VariablesProvider? argumentsProvider, VariablesProvider? localsProvider, DbgDotNetILInterpreterState state, string typeName, string methodName, DbgEvaluationOptions options, out DmdType expectedType) { var stateImpl = (DbgDotNetILInterpreterStateImpl)state; var debuggerRuntime = debuggerRuntimeFactory.Create(evalInfo.Runtime); debuggerRuntime.Runtime.Dispatcher.VerifyAccess(); var appDomain = evalInfo.Frame.AppDomain; if (appDomain is null) throw new ArgumentException("No AppDomain available"); var reflectionAssembly = stateImpl.GetOrCreateReflectionAssembly(appDomain); var methodState = stateImpl.GetOrCreateMethodInfoState(appDomain, typeName, methodName, genericTypeArguments, genericMethodArguments); DbgDotNetValueResult result = default; using (reflectionAssembly.AppDomain.AddTemporaryAssembly(reflectionAssembly)) { var ilvmState = methodState.ILVMExecuteState; if (ilvmState is null) { // This could fail so get it first var realMethod = evalInfo.Runtime.GetDotNetRuntime().GetFrameMethod(evalInfo) ?? throw new InvalidOperationException(); var type = reflectionAssembly.GetTypeThrow(typeName); Debug.Assert(type.GetGenericArguments().Count == genericTypeArguments.Count); if (genericTypeArguments.Count != 0) type = type.MakeGenericType(genericTypeArguments); const DmdBindingFlags bindingFlags = DmdBindingFlags.Static | DmdBindingFlags.Public | DmdBindingFlags.NonPublic; var method = type.GetMethod(methodName, bindingFlags) ?? throw new InvalidOperationException(); Debug.Assert(method.GetGenericArguments().Count == genericMethodArguments.Count); if (genericMethodArguments.Count != 0) method = method.MakeGenericMethod(genericMethodArguments); methodState.ExpectedType = method.ReturnType; ilvmState = stateImpl.ILVM.CreateExecuteState(method); methodState.ILVMExecuteState = ilvmState; methodState.RealMethodBody = realMethod.GetMethodBody(); } expectedType = methodState.ExpectedType!; debuggerRuntime.Initialize(evalInfo, methodState.RealMethodBody, argumentsProvider, localsProvider, (options & DbgEvaluationOptions.NoFuncEval) == 0); try { var execResult = stateImpl.ILVM.Execute(debuggerRuntime, ilvmState); var resultValue = debuggerRuntime.GetDotNetValue(execResult, expectedType); if (expectedType == expectedType.AppDomain.System_Void) { resultValue.Dispose(); resultValue = new NoResultValue(expectedType.AppDomain); } result = DbgDotNetValueResult.Create(resultValue); } catch (InterpreterException ie) { result = DbgDotNetValueResult.CreateError(GetErrorMessage(ie.Kind)); } catch (InterpreterMessageException ime) { result = DbgDotNetValueResult.CreateError(ime.Message); } catch (InterpreterThrownExceptionException thrownEx) { Debug.Assert(thrownEx.ThrownValue is DbgDotNetValue); if (thrownEx.ThrownValue is DbgDotNetValue thrownValue) result = DbgDotNetValueResult.CreateException(thrownValue); else result = DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError); } catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) { result = DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError); } finally { debuggerRuntime.Clear(result.Value); } } return result; } sealed class NoResultValue : DbgDotNetValue { public override DmdType Type { get; } public NoResultValue(DmdAppDomain appDomain) => Type = appDomain.System_Void; public override DbgDotNetRawValue GetRawValue() => new DbgDotNetRawValue(DbgSimpleValueType.Void); } static string GetErrorMessage(InterpreterExceptionKind kind) { switch (kind) { case InterpreterExceptionKind.TooManyInstructions: case InterpreterExceptionKind.InvalidMethodBody: case InterpreterExceptionKind.InstructionNotSupported: default: return PredefinedEvaluationErrorMessages.InternalDebuggerError; } } } }