/*
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 System.Runtime.CompilerServices;
using System.Threading;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.CallStack;
using dnSpy.Contracts.Debugger.DotNet.Disassembly;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.DotNet.Mono;
using dnSpy.Contracts.Debugger.Engine.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Contracts.Disassembly;
using dnSpy.Contracts.Metadata;
using dnSpy.Debugger.DotNet.Metadata;
using dnSpy.Debugger.DotNet.Mono.CallStack;
using dnSpy.Debugger.DotNet.Mono.Impl.Evaluation.Hooks;
using dnSpy.Debugger.DotNet.Mono.Properties;
using Mono.Debugger.Soft;
namespace dnSpy.Debugger.DotNet.Mono.Impl.Evaluation {
sealed class DbgMonoDebugInternalRuntimeImpl : DbgMonoDebugInternalRuntime, IDbgDotNetRuntime, IMonoDebugRuntime {
public override MonoDebugRuntimeKind Kind { get; }
public override DmdRuntime ReflectionRuntime { get; }
public override DbgRuntime Runtime { get; }
public DbgDotNetDispatcher Dispatcher { get; }
public DbgDotNetRuntimeFeatures Features { get; }
IMonoDebugValueConverter IMonoDebugRuntime.ValueConverter => monoDebugValueConverter;
readonly DbgEngineImpl engine;
readonly Dictionary classHooks;
readonly IMonoDebugValueConverter monoDebugValueConverter;
public DbgMonoDebugInternalRuntimeImpl(DbgEngineImpl engine, DbgRuntime runtime, DmdRuntime reflectionRuntime, MonoDebugRuntimeKind monoDebugRuntimeKind) {
this.engine = engine ?? throw new ArgumentNullException(nameof(engine));
Runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
ReflectionRuntime = reflectionRuntime ?? throw new ArgumentNullException(nameof(reflectionRuntime));
Kind = monoDebugRuntimeKind;
Dispatcher = new DbgDotNetDispatcherImpl(engine);
Features = CalculateFeatures(engine.MonoVirtualMachine);
reflectionRuntime.GetOrCreateData(() => runtime);
monoDebugValueConverter = new MonoDebugValueConverterImpl(this);
classHooks = new Dictionary();
foreach (var info in ClassHookProvider.Create(engine, this)) {
Debug2.Assert(info.Hook is not null);
Debug.Assert(!classHooks.ContainsKey(info.WellKnownType));
classHooks.Add(info.WellKnownType, info.Hook);
}
}
static DbgDotNetRuntimeFeatures CalculateFeatures(VirtualMachine vm) {
var res = DbgDotNetRuntimeFeatures.ObjectIds | DbgDotNetRuntimeFeatures.NoDereferencePointers;
if (!vm.Version.AtLeast(2, 24))
res |= DbgDotNetRuntimeFeatures.NoGenericMethods;
// We need FuncEvalOptions.ReturnOutThis support so func-eval of Task/ObjectIdForDebugger
// prop updates the struct's task field
if (!vm.Version.AtLeast(2, 35))
res |= DbgDotNetRuntimeFeatures.NoAsyncStepObjectId;
return res;
}
public ModuleId GetModuleId(DbgModule module) => engine.GetModuleId(module);
public DbgDotNetRawModuleBytes GetRawModuleBytes(DbgModule module) {
if (!module.IsDynamic)
return DbgDotNetRawModuleBytes.None;
if (Dispatcher.CheckAccess())
return GetRawModuleBytesCore(module);
return GetRawModuleBytesCore2(module);
DbgDotNetRawModuleBytes GetRawModuleBytesCore2(DbgModule module2) {
if (!Dispatcher.TryInvokeRethrow(() => GetRawModuleBytesCore(module2), out var result))
result = DbgDotNetRawModuleBytes.None;
return result;
}
}
DbgDotNetRawModuleBytes GetRawModuleBytesCore(DbgModule module) {
Dispatcher.VerifyAccess();
if (!module.IsDynamic)
return DbgDotNetRawModuleBytes.None;
return DbgDotNetRawModuleBytes.None;//TODO:
}
public bool TryGetMethodToken(DbgModule module, int methodToken, out int metadataMethodToken, out int metadataLocalVarSigTok) {
if (!module.IsDynamic) {
metadataMethodToken = 0;
metadataLocalVarSigTok = 0;
return false;
}
if (Dispatcher.CheckAccess())
return TryGetMethodTokenCore(module, methodToken, out metadataMethodToken, out metadataLocalVarSigTok);
return TryGetMethodTokenCore2(module, methodToken, out metadataMethodToken, out metadataLocalVarSigTok);
bool TryGetMethodTokenCore2(DbgModule module2, int methodToken2, out int metadataMethodToken2, out int metadataLocalVarSigTok2) {
int tmpMetadataMethodToken = 0, tmpMetadataLocalVarSigTok = 0;
if (!Dispatcher.TryInvokeRethrow(() => {
var res = TryGetMethodTokenCore(module2, methodToken2, out var metadataMethodToken3, out var metadataLocalVarSigTok3);
tmpMetadataMethodToken = metadataMethodToken3;
tmpMetadataLocalVarSigTok = metadataLocalVarSigTok3;
return res;
}, out var result)) {
metadataMethodToken2 = 0;
metadataLocalVarSigTok2 = 0;
return false;
}
metadataMethodToken2 = tmpMetadataMethodToken;
metadataLocalVarSigTok2 = tmpMetadataLocalVarSigTok;
return result;
}
}
bool TryGetMethodTokenCore(DbgModule module, int methodToken, out int metadataMethodToken, out int metadataLocalVarSigTok) {
Dispatcher.VerifyAccess();
//TODO:
metadataMethodToken = 0;
metadataLocalVarSigTok = 0;
return false;
}
sealed class GetFrameMethodState {
public bool Initialized;
public DmdMethodBase? Method;
}
public DmdMethodBase? GetFrameMethod(DbgEvaluationInfo evalInfo) {
if (Dispatcher.CheckAccess())
return GetFrameMethodCore(evalInfo);
return GetFrameMethod2(evalInfo);
DmdMethodBase? GetFrameMethod2(DbgEvaluationInfo evalInfo2) {
Dispatcher.TryInvokeRethrow(() => GetFrameMethodCore(evalInfo2), out var result);
return result;
}
}
DmdMethodBase? GetFrameMethodCore(DbgEvaluationInfo evalInfo) {
Dispatcher.VerifyAccess();
var state = evalInfo.Frame.GetOrCreateData();
if (!state.Initialized) {
evalInfo.CancellationToken.ThrowIfCancellationRequested();
if (ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame)) {
ilFrame.GetFrameMethodInfo(out var module, out var methodMetadataToken, out var genericTypeArguments, out var genericMethodArguments);
// Don't throw if it fails to resolve. Callers must be able to handle null return values
state.Method = TryGetMethod(module, methodMetadataToken, genericTypeArguments, genericMethodArguments);
}
state.Initialized = true;
}
return state.Method;
}
static DmdMethodBase? TryGetMethod(DmdModule module, int methodMetadataToken, IList genericTypeArguments, IList genericMethodArguments) {
var method = module?.ResolveMethod(methodMetadataToken, (IList?)null, null, DmdResolveOptions.None);
if (method is not null) {
if (genericTypeArguments.Count != 0) {
var type = method.ReflectedType!.MakeGenericType(genericTypeArguments);
method = type.GetMethod(method.Module, method.MetadataToken, throwOnError: true)!;
}
if (genericMethodArguments.Count != 0)
method = ((DmdMethodInfo)method).MakeGenericMethod(genericMethodArguments);
}
return method;
}
TypeMirror GetType(DbgEvaluationInfo evalInfo, DmdType type) =>
MonoDebugTypeCreator.GetType(engine, type, engine.CreateMonoTypeLoader(evalInfo));
public DbgDotNetValue? LoadFieldAddress(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) => null;
public DbgDotNetValueResult LoadField(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) {
if (Dispatcher.CheckAccess())
return LoadFieldCore(evalInfo, obj, field);
return LoadField2(evalInfo, obj, field);
DbgDotNetValueResult LoadField2(DbgEvaluationInfo evalInfo2, DbgDotNetValue? obj2, DmdFieldInfo field2) {
if (!Dispatcher.TryInvokeRethrow(() => LoadFieldCore(evalInfo2, obj2, field2), out var result))
result = DbgDotNetValueResult.CreateError(DispatcherConstants.ProcessExitedError);
return result;
}
}
DbgDotNetValueResult LoadFieldCore(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) {
Dispatcher.VerifyAccess();
try {
var info = GetFieldValueLocationCore(evalInfo, obj, field);
if (info.errorMessage is not null)
return DbgDotNetValueResult.CreateError(info.errorMessage);
return DbgDotNetValueResult.Create(engine.CreateDotNetValue_MonoDebug(info.valueLocation!));
}
catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) {
return DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError);
}
}
(ValueLocation? valueLocation, string? errorMessage) GetFieldValueLocationCore(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) {
if (!ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame))
return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
var fieldDeclType = field.DeclaringType!;
bool canNotAccessField = !engine.MonoVirtualMachine.Version.AtLeast(2, 15) && fieldDeclType.ContainsGenericParameters;
var monoFieldDeclType = GetType(evalInfo, fieldDeclType);
if (obj is null) {
if (!field.IsStatic)
return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
if (field.IsLiteral) {
var monoValue = MonoValueFactory.TryCreateSyntheticValue(engine, monoFieldDeclType.Assembly.Domain, field.FieldType, field.GetRawConstantValue()) ??
new PrimitiveValue(monoFieldDeclType.VirtualMachine, ElementType.Object, null);
return (new NoValueLocation(field.FieldType, monoValue), null);
}
else {
if (canNotAccessField)
return (null, dnSpy_Debugger_DotNet_Mono_Resources.Error_CannotAccessMemberRuntimeLimitations);
var monoField = MemberMirrorUtils.GetMonoField(monoFieldDeclType, field);
InitializeStaticConstructor(evalInfo, ilFrame, fieldDeclType, monoFieldDeclType);
return (new StaticFieldValueLocation(field.FieldType, ilFrame.MonoFrame.Thread, monoField), null);
}
}
else {
if (field.IsStatic)
return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
var objImp = obj as DbgDotNetValueImpl ?? throw new InvalidOperationException();
var monoField = MemberMirrorUtils.GetMonoField(monoFieldDeclType, field);
switch (objImp.Value) {
case ObjectMirror om:
if (canNotAccessField)
return (null, dnSpy_Debugger_DotNet_Mono_Resources.Error_CannotAccessMemberRuntimeLimitations);
return (new ReferenceTypeFieldValueLocation(field.FieldType, om, monoField), null);
case StructMirror sm:
return (new ValueTypeFieldValueLocation(field.FieldType, objImp.ValueLocation, sm, monoField), null);
case PrimitiveValue pv:
return (null, "NYI");//TODO:
default:
// Unreachable
return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
}
}
}
sealed class StaticConstructorInitializedState {
public volatile int Initialized;
}
void InitializeStaticConstructor(DbgEvaluationInfo evalInfo, ILDbgEngineStackFrame ilFrame, DmdType type, TypeMirror monoType) {
if (engine.CheckFuncEval(evalInfo.Context) is not null)
return;
var state = type.GetOrCreateData();
if (state.Initialized > 0 || Interlocked.Exchange(ref state.Initialized, 1) != 0)
return;
if (engine.MonoVirtualMachine.Version.AtLeast(2, 23) && monoType.IsInitialized)
return;
var cctor = type.TypeInitializer;
if (cctor is not null) {
var fields = type.DeclaredFields;
for (int i = 0; i < fields.Count; i++) {
var field = fields[i];
if (!field.IsStatic || field.IsLiteral)
continue;
var monoField = MemberMirrorUtils.GetMonoField(monoType, fields, i);
Value fieldValue;
try {
fieldValue = monoType.GetValue(monoField, ilFrame.MonoFrame.Thread);
}
catch {
break;
}
if (fieldValue is not null) {
if (fieldValue is PrimitiveValue pv && pv.Value is null)
continue;
if (field.FieldType.IsValueType) {
if (!IsZero(fieldValue, 0))
return;
}
else {
// It's a reference type and not null, so the field has been initialized
return;
}
}
}
}
DbgDotNetValue? dnObjValue = null;
try {
var reflectionAppDomain = type.AppDomain;
var monoObjValue = monoType.GetTypeObject();
var objValueType = engine.GetReflectionType(reflectionAppDomain, monoObjValue.Type, null);
var objValueLocation = new NoValueLocation(objValueType, monoObjValue);
dnObjValue = engine.CreateDotNetValue_MonoDebug(objValueLocation);
RuntimeHelpersRunClassConstructor(evalInfo, type, dnObjValue);
}
finally {
dnObjValue?.Dispose();
}
}
// Calls System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor():
// RuntimeHelpers.RunClassConstructor(obj.GetType().TypeHandle);
bool RuntimeHelpersRunClassConstructor(DbgEvaluationInfo evalInfo, DmdType type, DbgDotNetValue objValue) {
DbgDotNetValueResult typeHandleRes = default;
DbgDotNetValueResult res = default;
try {
var reflectionAppDomain = type.AppDomain;
var runtimeTypeHandleType = reflectionAppDomain.GetWellKnownType(DmdWellKnownType.System_RuntimeTypeHandle, isOptional: true);
Debug2.Assert(runtimeTypeHandleType is not null);
if (runtimeTypeHandleType is null)
return false;
var getTypeHandleMethod = objValue.Type.GetMethod("get_" + nameof(Type.TypeHandle), DmdSignatureCallingConvention.Default | DmdSignatureCallingConvention.HasThis, 0, runtimeTypeHandleType, Array.Empty(), throwOnError: false);
Debug2.Assert(getTypeHandleMethod is not null);
if (getTypeHandleMethod is null)
return false;
typeHandleRes = engine.FuncEvalCall_MonoDebug(evalInfo, getTypeHandleMethod, objValue, Array.Empty