/*
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.Collections.ObjectModel;
using System.Diagnostics;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.DotNet.Evaluation.ExpressionCompiler;
using dnSpy.Contracts.Debugger.Engine.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Debugger.DotNet.Interpreter;
using dnSpy.Debugger.DotNet.Metadata;
using dnSpy.Debugger.DotNet.Properties;
namespace dnSpy.Debugger.DotNet.Evaluation.Engine.Interpreter {
abstract class DebuggerRuntime2 : DebuggerRuntime {
public abstract IDbgDotNetRuntime Runtime { get; }
public abstract void Initialize(DbgEvaluationInfo evalInfo, DmdMethodBody? realMethodBody, VariablesProvider? argumentsProvider, VariablesProvider? localsProvider, bool canFuncEval);
public abstract void Clear(DbgDotNetValue? returnValue);
public abstract DbgDotNetValue GetDotNetValue(ILValue value, DmdType? targetType = null);
}
sealed class DebuggerRuntimeImpl : DebuggerRuntime2, IDebuggerRuntime {
public override int PointerSize { get; }
public override IDbgDotNetRuntime Runtime => runtime;
readonly DbgObjectIdService dbgObjectIdService;
readonly IDbgDotNetRuntime runtime;
readonly DotNetClassHook[] anyClassHooks;
readonly Dictionary classHooks;
readonly List valuesToDispose;
readonly InterpreterLocalsProvider interpreterLocalsProvider;
public DebuggerRuntimeImpl(DbgObjectIdService dbgObjectIdService, IDbgDotNetRuntime runtime, int pointerSize, DotNetClassHookFactory[] dotNetClassHookFactories) {
if (dotNetClassHookFactories is null)
throw new ArgumentNullException(nameof(dotNetClassHookFactories));
argumentsProvider = null!;
evalInfo = null!;
this.dbgObjectIdService = dbgObjectIdService ?? throw new ArgumentNullException(nameof(dbgObjectIdService));
this.runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
valuesToDispose = new List();
interpreterLocalsProvider = new InterpreterLocalsProvider(this);
PointerSize = pointerSize;
var anyClassHooksList = new List();
classHooks = new Dictionary(DmdTypeNameEqualityComparer.Instance);
foreach (var factory in dotNetClassHookFactories) {
foreach (var info in factory.Create(this)) {
Debug2.Assert(info.Hook is not null);
if (info.WellKnownType is null && info.TypeName is null)
anyClassHooksList.Add(info.Hook);
else {
DmdTypeName typeName;
if (info.WellKnownType is not null)
typeName = DmdWellKnownTypeUtils.GetTypeName(info.WellKnownType.Value);
else {
Debug2.Assert(info.TypeName is not null);
typeName = info.TypeName.Value;
}
Debug.Assert(!classHooks.ContainsKey(typeName));
classHooks[typeName] = info.Hook;
}
}
}
anyClassHooks = anyClassHooksList.ToArray();
}
VariablesProvider DefaultArgumentsProvider => defaultArgumentsProvider ??= new DefaultArgumentsProviderImpl(runtime);
VariablesProvider? defaultArgumentsProvider;
VariablesProvider DefaultLocalsProvider => defaultLocalsProvider ??= new DefaultLocalsProviderImpl(runtime);
VariablesProvider? defaultLocalsProvider;
VariablesProvider argumentsProvider;
DbgEvaluationInfo evalInfo;
bool canFuncEval;
public override void Initialize(DbgEvaluationInfo evalInfo, DmdMethodBody? realMethodBody, VariablesProvider? argumentsProvider, VariablesProvider? localsProvider, bool canFuncEval) {
Debug2.Assert(this.evalInfo is null);
if (this.evalInfo is not null)
throw new InvalidOperationException();
this.evalInfo = evalInfo;
this.canFuncEval = canFuncEval;
this.argumentsProvider = argumentsProvider ?? DefaultArgumentsProvider;
interpreterLocalsProvider.Initialize(realMethodBody, localsProvider ?? DefaultLocalsProvider);
Debug.Assert(valuesToDispose.Count == 0);
}
public override void Initialize(DmdMethodBase method, DmdMethodBody body) {
argumentsProvider.Initialize(evalInfo, method, body);
interpreterLocalsProvider.Initialize(evalInfo, method, body);
}
public override void Clear(DbgDotNetValue? returnValue) {
evalInfo = null!;
canFuncEval = false;
Debug2.Assert(argumentsProvider is not null);
foreach (var v in valuesToDispose) {
if (v != returnValue && argumentsProvider.CanDispose(v) && interpreterLocalsProvider.CanDispose(v))
v.Dispose();
}
valuesToDispose.Clear();
argumentsProvider.Clear();
interpreterLocalsProvider.Clear();
argumentsProvider = null!;
}
public override DbgDotNetValue GetDotNetValue(ILValue value, DmdType? targetType = null) {
targetType ??= value.Type;
var dnValue = TryGetDotNetValue(value, value.IsNull ? targetType : value.Type, canCreateValue: true);
if (dnValue is not null)
return dnValue;
throw new InvalidOperationException();//TODO:
}
DbgDotNetValue? TryCreateSyntheticValue(DmdType type, object? value) {
var dnValue = SyntheticValueFactory.TryCreateSyntheticValue(type, value);
if (dnValue is not null)
RecordValue(dnValue);
return dnValue;
}
DbgDotNetValue? TryGetDotNetValue(ILValue value, bool canCreateValue) => TryGetDotNetValue(value, value.Type, canCreateValue);
DbgDotNetValue? TryGetDotNetValue(ILValue value, DmdType? valueType, bool canCreateValue) {
if (value is IDebuggerRuntimeILValue rtValue)
return rtValue.GetDotNetValue();
if (canCreateValue) {
if (value.IsNull)
return new SyntheticNullValue(valueType ?? evalInfo.Frame.Module?.AppDomain?.GetReflectionAppDomain()?.System_Void);
object? newValue;
var type = valueType;
switch (value.Kind) {
case ILValueKind.Int32:
int v32 = ((ConstantInt32ILValue)value).Value;
switch (DmdType.GetTypeCode(type)) {
case TypeCode.Boolean: newValue = v32 != 0; break;
case TypeCode.Char: newValue = (char)v32; break;
case TypeCode.SByte: newValue = (sbyte)v32; break;
case TypeCode.Byte: newValue = (byte)v32; break;
case TypeCode.Int16: newValue = (short)v32; break;
case TypeCode.UInt16: newValue = (ushort)v32; break;
case TypeCode.Int32: newValue = v32; break;
case TypeCode.UInt32: newValue = (uint)v32; break;
default: newValue = null; break;
}
break;
case ILValueKind.Int64:
long v64 = ((ConstantInt64ILValue)value).Value;
switch (DmdType.GetTypeCode(type)) {
case TypeCode.Int64: newValue = v64; break;
case TypeCode.UInt64: newValue = (ulong)v64; break;
default: newValue = null; break;
}
break;
case ILValueKind.Float:
double r8 = ((ConstantFloatILValue)value).Value;
switch (DmdType.GetTypeCode(type)) {
case TypeCode.Single: newValue = (float)r8; break;
case TypeCode.Double: newValue = r8; break;
default: newValue = null; break;
}
break;
case ILValueKind.NativeInt:
if (value is ConstantNativeIntILValue ci) {
if (type == type!.AppDomain.System_IntPtr) {
if (PointerSize == 4)
newValue = new IntPtr(ci.Value32);
else
newValue = new IntPtr(ci.Value64);
}
else if (type == type.AppDomain.System_UIntPtr || type.IsPointer || type.IsFunctionPointer) {
if (PointerSize == 4)
newValue = new UIntPtr(ci.UnsignedValue32);
else
newValue = new UIntPtr(ci.UnsignedValue64);
}
else
newValue = null;
}
else
newValue = null;
break;
case ILValueKind.Type:
if (value is ConstantStringILValueImpl sv)
newValue = sv.Value;
else
newValue = null;
break;
default:
newValue = null;
break;
}
if (newValue is not null) {
var dnValue = TryCreateSyntheticValue(type!, newValue);
if (dnValue is not null)
return dnValue;
return RecordValue(runtime.CreateValue(evalInfo, newValue));
}
}
return null;
}
internal object? GetDebuggerValue(ILValue value, DmdType targetType) {
var dnValue = TryGetDotNetValue(value, targetType, canCreateValue: false);
if (dnValue is not null)
return dnValue;
if (value.IsNull)
return null;
var targetTypeCode = DmdType.GetTypeCode(targetType);
switch (value.Kind) {
case ILValueKind.Int32:
int v32 = ((ConstantInt32ILValue)value).Value;
switch (targetTypeCode) {
case TypeCode.Boolean: return v32 != 0;
case TypeCode.Char: return (char)v32;
case TypeCode.SByte: return (sbyte)v32;
case TypeCode.Byte: return (byte)v32;
case TypeCode.Int16: return (short)v32;
case TypeCode.UInt16: return (ushort)v32;
case TypeCode.Int32: return v32;
case TypeCode.UInt32: return (uint)v32;
}
break;
case ILValueKind.Int64:
long v64 = ((ConstantInt64ILValue)value).Value;
switch (targetTypeCode) {
case TypeCode.Int64: return v64;
case TypeCode.UInt64: return (ulong)v64;
}
break;
case ILValueKind.Float:
double r8 = ((ConstantFloatILValue)value).Value;
switch (targetTypeCode) {
case TypeCode.Single: return (float)r8;
case TypeCode.Double: return r8;
}
break;
case ILValueKind.NativeInt:
if (value is ConstantNativeIntILValue ci) {
if (targetType.IsPointer || targetType.IsFunctionPointer || targetType == targetType.AppDomain.System_IntPtr) {
if (PointerSize == 4)
return new IntPtr(ci.Value32);
return new IntPtr(ci.Value64);
}
else if (targetType == targetType.AppDomain.System_UIntPtr) {
if (PointerSize == 4)
return new UIntPtr(ci.UnsignedValue32);
return new UIntPtr(ci.UnsignedValue64);
}
}
break;
case ILValueKind.Type:
if (value is ConstantStringILValueImpl sv)
return sv.Value;
break;
}
Debug.Fail($"Unknown value can't be converted to {targetType.FullName}: {value}");
throw new InvalidOperationException();
}
internal ILValue CreateILValue(DbgDotNetValueResult result) {
if (result.HasError)
throw new InterpreterMessageException(result.ErrorMessage!);
if (result.ValueIsException)
throw new InterpreterThrownExceptionException(result.Value!);
var dnValue = result.Value;
if (dnValue is null)
throw new InterpreterMessageException(PredefinedEvaluationErrorMessages.InternalDebuggerError);
return CreateILValue(dnValue);
}
internal DbgDotNetValue RecordValue(DbgDotNetValueResult result) {
if (result.HasError)
throw new InterpreterMessageException(result.ErrorMessage!);
if (result.ValueIsException)
throw new InterpreterThrownExceptionException(result.Value!);
var dnValue = result.Value;
if (dnValue is null)
throw new InterpreterMessageException(PredefinedEvaluationErrorMessages.InternalDebuggerError);
return RecordValue(dnValue);
}
internal DbgDotNetValue RecordValue(DbgDotNetValue value) {
try {
evalInfo.CancellationToken.ThrowIfCancellationRequested();
Debug2.Assert(value is not null);
valuesToDispose.Add(value);
return value;
}
catch {
value!.Dispose();
throw;
}
}
internal ILValue CreateILValue(DbgDotNetValue value) {
try {
Debug2.Assert(value is not null);
valuesToDispose.Add(value);
return CreateILValueCore(value);
}
catch {
value!.Dispose();
throw;
}
}
ILValue CreateILValueCore(DbgDotNetValue value) {
if (value.Type.IsByRef)
return new ByRefILValueImpl(this, value);
if (value.Type.IsPointer)
return new PointerILValue(this, value);
if (value.IsNull)
return new NullObjectRefILValueImpl(value);
if (value.Type.IsArray)
return new ArrayILValue(this, value);
var rawValue = value.GetRawValue();
var objValue = rawValue.RawValue;
switch (rawValue.ValueType) {
case DbgSimpleValueType.Other:
if (rawValue.HasRawValue && objValue is null)
return new NullObjectRefILValueImpl(value);
return new TypeILValueImpl(this, value);
case DbgSimpleValueType.Decimal:
case DbgSimpleValueType.DateTime:
return new TypeILValueImpl(this, value);
case DbgSimpleValueType.Void:
throw new InvalidOperationException();
case DbgSimpleValueType.Boolean:
return new ConstantInt32ILValueImpl(value, (bool)objValue! ? 1 : 0);
case DbgSimpleValueType.Char1:
return new ConstantInt32ILValueImpl(value, (byte)objValue!);
case DbgSimpleValueType.CharUtf16:
return new ConstantInt32ILValueImpl(value, (char)objValue!);
case DbgSimpleValueType.Int8:
return new ConstantInt32ILValueImpl(value, (sbyte)objValue!);
case DbgSimpleValueType.Int16:
return new ConstantInt32ILValueImpl(value, (short)objValue!);
case DbgSimpleValueType.Int32:
return new ConstantInt32ILValueImpl(value, (int)objValue!);
case DbgSimpleValueType.Int64:
return new ConstantInt64ILValueImpl(value, (long)objValue!);
case DbgSimpleValueType.UInt8:
return new ConstantInt32ILValueImpl(value, (byte)objValue!);
case DbgSimpleValueType.UInt16:
return new ConstantInt32ILValueImpl(value, (ushort)objValue!);
case DbgSimpleValueType.UInt32:
return new ConstantInt32ILValueImpl(value, (int)(uint)objValue!);
case DbgSimpleValueType.UInt64:
return new ConstantInt64ILValueImpl(value, (long)(ulong)objValue!);
case DbgSimpleValueType.Float32:
return new ConstantFloatILValueImpl(value, (float)objValue!);
case DbgSimpleValueType.Float64:
return new ConstantFloatILValueImpl(value, (double)objValue!);
case DbgSimpleValueType.Ptr32:
if (PointerSize != 4)
throw new InvalidOperationException();
return ConstantNativeIntILValueImpl.Create32(value, (int)(uint)objValue!);
case DbgSimpleValueType.Ptr64:
if (PointerSize != 8)
throw new InvalidOperationException();
return ConstantNativeIntILValueImpl.Create64(value, (long)(ulong)objValue!);
case DbgSimpleValueType.StringUtf16:
return new ConstantStringILValueImpl(this, value, (string)objValue!);
default:
Debug.Fail($"Unknown type: {rawValue.ValueType}");
throw new InvalidOperationException();
}
}
DbgDotNetValueResult GetArgument(int index) => argumentsProvider.GetVariable(index);
string? SetArgument(int index, DmdType targetType, object? value) => argumentsProvider.SetVariable(index, targetType, value);
DbgDotNetValueResult GetLocal(int index) => interpreterLocalsProvider.GetVariable(index);
string? SetLocal(int index, DmdType targetType, object? value) => interpreterLocalsProvider.SetVariable(index, targetType, value);
public override ILValue? LoadArgument(int index) => CreateILValue(GetArgument(index));
internal DbgDotNetValue LoadArgument2(int index) => RecordValue(GetArgument(index));
public override ILValue? LoadLocal(int index) => CreateILValue(GetLocal(index));
internal DbgDotNetValue LoadLocal2(int index) => RecordValue(GetLocal(index));
public override ILValue? LoadArgumentAddress(int index, DmdType type) {
var addrValue = argumentsProvider.GetValueAddress(index, type);
if (addrValue is not null) {
Debug.Assert(addrValue.Type.IsByRef);
return new ByRefILValueImpl(this, RecordValue(addrValue));
}
return new ArgumentAddress(this, type, index);
}
public override ILValue? LoadLocalAddress(int index, DmdType type) {
var addrValue = interpreterLocalsProvider.GetValueAddress(index, type);
if (addrValue is not null) {
Debug.Assert(addrValue.Type.IsByRef);
return new ByRefILValueImpl(this, RecordValue(addrValue));
}
return new LocalAddress(this, type, index);
}
public override bool StoreArgument(int index, DmdType type, ILValue value) => StoreArgument2(index, type, GetDebuggerValue(value, type));
internal bool StoreArgument2(int index, DmdType targetType, object? value) {
var error = SetArgument(index, targetType, value);
if (error is not null)
throw new InterpreterMessageException(error);
return true;
}
public override bool StoreLocal(int index, DmdType type, ILValue value) => StoreLocal2(index, type, GetDebuggerValue(value, type));
internal bool StoreLocal2(int index, DmdType targetType, object? value) {
var error = SetLocal(index, targetType, value);
if (error is not null)
throw new InterpreterMessageException(error);
return true;
}
public override ILValue? CreateSZArray(DmdType elementType, long length) {
if (length < 0 || length > int.MaxValue)
return null;
var res = runtime.CreateSZArray(evalInfo, elementType, (int)length);
return CreateILValue(res);
}
public override ILValue? CreateRuntimeTypeHandle(DmdType type) => new RuntimeTypeHandleILValue(this, type);
internal DbgDotNetValue CreateRuntimeTypeHandleCore(DmdType type) {
if (!canFuncEval)
throw new InterpreterMessageException(PredefinedEvaluationErrorMessages.FuncEvalDisabled);
var appDomain = type.AppDomain;
var methodGetType = appDomain.System_Type.GetMethod(nameof(Type.GetType), DmdSignatureCallingConvention.Default, 0, appDomain.System_Type, new[] { appDomain.System_String }, throwOnError: true)!;
var typeValue = RecordValue(runtime.Call(evalInfo, null, methodGetType, new[] { type.AssemblyQualifiedName }, DbgDotNetInvokeOptions.None));
var runtimeTypeHandleType = appDomain.GetWellKnownType(DmdWellKnownType.System_RuntimeTypeHandle);
var getTypeHandleMethod = typeValue.Type.GetMethod("get_" + nameof(Type.TypeHandle), DmdSignatureCallingConvention.Default | DmdSignatureCallingConvention.HasThis, 0, runtimeTypeHandleType, Array.Empty(), throwOnError: true)!;
return RecordValue(runtime.Call(evalInfo, typeValue, getTypeHandleMethod, Array.Empty