/*
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.Diagnostics;
using System.Threading;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.Code;
using dnSpy.Contracts.Debugger.DotNet.Code;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.DotNet.Evaluation.ExpressionCompiler;
using dnSpy.Contracts.Debugger.DotNet.Evaluation.Formatters;
using dnSpy.Contracts.Debugger.Engine.Evaluation;
using dnSpy.Contracts.Debugger.Engine.Evaluation.Internal;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Metadata;
using dnSpy.Debugger.DotNet.Code;
using dnSpy.Debugger.DotNet.Evaluation.Engine.Interpreter;
namespace dnSpy.Debugger.DotNet.Evaluation.Engine {
sealed class DbgEngineLanguageImpl : DbgEngineLanguage {
public override string Name { get; }
public override string DisplayName { get; }
public override DbgEngineExpressionEvaluator ExpressionEvaluator { get; }
public override DbgEngineFormatter Formatter { get; }
public override DbgEngineLocalsValueNodeProvider LocalsProvider { get; }
public override DbgEngineValueNodeProvider AutosProvider { get; }
public override DbgEngineValueNodeProvider ExceptionsProvider { get; }
public override DbgEngineValueNodeProvider ReturnValuesProvider { get; }
public override DbgEngineValueNodeProvider TypeVariablesProvider { get; }
public override DbgEngineValueNodeFactory ValueNodeFactory { get; }
readonly DbgMethodDebugInfoProvider dbgMethodDebugInfoProvider;
readonly IDecompiler decompiler;
readonly DbgDotNetExpressionCompiler expressionCompiler;
readonly IDebuggerDisplayAttributeEvaluator debuggerDisplayAttributeEvaluator;
public DbgEngineLanguageImpl(DbgModuleReferenceProvider dbgModuleReferenceProvider, string name, string displayName, DbgDotNetExpressionCompiler expressionCompiler, DbgMethodDebugInfoProvider dbgMethodDebugInfoProvider, IDecompiler decompiler, DbgDotNetFormatter formatter, DbgDotNetEngineValueNodeFactory valueNodeFactory, DbgDotNetILInterpreter dnILInterpreter, DbgAliasProvider dbgAliasProvider, IPredefinedEvaluationErrorMessagesHelper predefinedEvaluationErrorMessagesHelper) {
if (dbgModuleReferenceProvider is null)
throw new ArgumentNullException(nameof(dbgModuleReferenceProvider));
if (formatter is null)
throw new ArgumentNullException(nameof(formatter));
if (valueNodeFactory is null)
throw new ArgumentNullException(nameof(valueNodeFactory));
if (dnILInterpreter is null)
throw new ArgumentNullException(nameof(dnILInterpreter));
if (dbgAliasProvider is null)
throw new ArgumentNullException(nameof(dbgAliasProvider));
if (predefinedEvaluationErrorMessagesHelper is null)
throw new ArgumentNullException(nameof(predefinedEvaluationErrorMessagesHelper));
Name = name ?? throw new ArgumentNullException(nameof(name));
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
this.dbgMethodDebugInfoProvider = dbgMethodDebugInfoProvider ?? throw new ArgumentNullException(nameof(dbgMethodDebugInfoProvider));
this.expressionCompiler = expressionCompiler ?? throw new ArgumentNullException(nameof(expressionCompiler));
this.decompiler = decompiler ?? throw new ArgumentNullException(nameof(decompiler));
var expressionEvaluator = new DbgEngineExpressionEvaluatorImpl(dbgModuleReferenceProvider, expressionCompiler, dnILInterpreter, dbgAliasProvider, predefinedEvaluationErrorMessagesHelper);
ExpressionEvaluator = expressionEvaluator;
Formatter = new DbgEngineFormatterImpl(formatter);
LocalsProvider = new DbgEngineLocalsProviderImpl(dbgModuleReferenceProvider, expressionCompiler, valueNodeFactory, dnILInterpreter, dbgAliasProvider);
AutosProvider = new DbgEngineAutosProviderImpl(valueNodeFactory);
ExceptionsProvider = new DbgEngineExceptionsProviderImpl(valueNodeFactory);
ReturnValuesProvider = new DbgEngineReturnValuesProviderImpl(valueNodeFactory);
TypeVariablesProvider = new DbgEngineTypeVariablesProviderImpl(valueNodeFactory);
ValueNodeFactory = new DbgEngineValueNodeFactoryImpl(expressionEvaluator, valueNodeFactory, formatter);
debuggerDisplayAttributeEvaluator = expressionEvaluator;
}
readonly struct DbgLanguageDebugInfoKey {
readonly uint token;
readonly DbgModule? module;
readonly ModuleId moduleId;
readonly int refreshedVersion;
public DbgLanguageDebugInfoKey(DbgModule module, uint token) {
this.token = token;
moduleId = default;
this.module = module;
refreshedVersion = module.RefreshedVersion;
}
public DbgLanguageDebugInfoKey(ModuleId moduleId, uint token) {
this.token = token;
this.moduleId = moduleId;
module = null;
refreshedVersion = 0;
}
public bool Equals(DbgLanguageDebugInfoKey other) =>
token == other.token &&
module == other.module &&
refreshedVersion == other.refreshedVersion &&
moduleId == other.moduleId;
}
sealed class RuntimeState {
public readonly object LockObj = new object();
public const int MAX_CACHED_DEBUG_INFOS = 5;
public readonly List<(DbgLanguageDebugInfoKey key, DbgLanguageDebugInfo debugInfo)> DebugInfos = new List<(DbgLanguageDebugInfoKey key, DbgLanguageDebugInfo debugInfo)>(MAX_CACHED_DEBUG_INFOS);
}
public override void InitializeContext(DbgEvaluationContext context, DbgCodeLocation? location, CancellationToken cancellationToken) {
Debug2.Assert(context.Runtime.GetDotNetRuntime() is not null);
IDebuggerDisplayAttributeEvaluatorUtils.Initialize(context, debuggerDisplayAttributeEvaluator);
// Needed by DebuggerRuntimeImpl (calls expressionCompiler.TryGetAliasInfo())
context.GetOrCreateData(() => expressionCompiler);
if ((context.Options & DbgEvaluationContextOptions.NoMethodBody) == 0 && location is IDbgDotNetCodeLocation loc) {
var state = StateWithKey.GetOrCreate(context.Runtime, decompiler);
var debugInfo = GetOrCreateDebugInfo(context, state, loc, cancellationToken);
if (debugInfo is not null)
DbgLanguageDebugInfoExtensions.SetLanguageDebugInfo(context, debugInfo);
}
}
//TODO: If decompiler settings change, we need to invalidate the cached data in DbgEvaluationContext, see decompiler.Settings.VersionChanged
DbgLanguageDebugInfo? GetOrCreateDebugInfo(DbgEvaluationContext context, RuntimeState state, IDbgDotNetCodeLocation location, CancellationToken cancellationToken) {
DbgLanguageDebugInfoKey key;
if (location.DbgModule is DbgModule dbgModule)
key = new DbgLanguageDebugInfoKey(dbgModule, location.Token);
else
key = new DbgLanguageDebugInfoKey(location.Module, location.Token);
var debugInfos = state.DebugInfos;
lock (state.LockObj) {
if (debugInfos.Count > 0 && debugInfos[0].debugInfo.MethodDebugInfo.DebugInfoVersion != decompiler.Settings.Version)
debugInfos.Clear();
for (int i = debugInfos.Count - 1; i >= 0; i--) {
var info = debugInfos[i];
if (info.key.Equals(key)) {
if (i != debugInfos.Count - 1) {
debugInfos.RemoveAt(i);
debugInfos.Add(info);
}
return info.debugInfo;
}
}
}
var debugInfo = CreateDebugInfo(context, location, cancellationToken);
if (debugInfo is null)
return null;
lock (state.LockObj) {
if (debugInfos.Count == RuntimeState.MAX_CACHED_DEBUG_INFOS)
debugInfos.RemoveAt(0);
debugInfos.Add((key, debugInfo));
}
return debugInfo;
}
DbgLanguageDebugInfo? CreateDebugInfo(DbgEvaluationContext context, IDbgDotNetCodeLocation location, CancellationToken cancellationToken) {
var result = dbgMethodDebugInfoProvider.GetMethodDebugInfo(context.Runtime, decompiler, location, cancellationToken);
if (result.DebugInfo is null)
return null;
var runtime = context.Runtime.GetDotNetRuntime();
if (location.DbgModule is null || !runtime.TryGetMethodToken(location.DbgModule, (int)location.Token, out int methodToken, out int localVarSigTok)) {
methodToken = (int)location.Token;
localVarSigTok = (int)((result.StateMachineDebugInfo ?? result.DebugInfo)?.Method.Body?.LocalVarSigTok ?? 0);
}
return new DbgLanguageDebugInfo(result.DebugInfo, methodToken, localVarSigTok, result.MethodVersion, location.Offset);
}
}
}