214 lines
9.6 KiB
C#
214 lines
9.6 KiB
C#
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
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<DmdType> genericTypeArguments, IList<DmdType> 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<DmdType> genericTypeArguments, IList<DmdType> 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<DmdType>, IList<DmdType>, MethodInfoState)>();
|
|
assemblies = new List<(int, DmdAssembly)>();
|
|
this.assemblyBytes = assemblyBytes ?? throw new ArgumentNullException(nameof(assemblyBytes));
|
|
}
|
|
|
|
public MethodInfoState GetOrCreateMethodInfoState(DbgAppDomain appDomain, string typeName, string methodName, IList<DmdType> genericTypeArguments, IList<DmdType> 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<DmdType> a, IList<DmdType> 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<DmdLazyMetadataBytes> 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<DmdType> genericTypeArguments, IList<DmdType> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|