2021-09-20 18:20:01 +02:00

215 lines
8.4 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.Linq;
using dnSpy.Contracts.App;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.DotNet.Metadata;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Documents.TreeView;
using dnSpy.Debugger.DotNet.UI;
namespace dnSpy.Debugger.DotNet.Metadata {
abstract class ClassLoaderFactory {
public abstract ClassLoader Create(DbgRuntime runtime, DbgDynamicModuleProvider dbgDynamicModuleProvider);
}
[Export(typeof(ClassLoaderFactory))]
sealed class ClassLoaderFactoryImpl : ClassLoaderFactory {
readonly UIDispatcher uiDispatcher;
readonly Lazy<IDocumentTreeView> documentTreeView;
readonly Lazy<IDocumentTabService> documentTabService;
readonly Lazy<ShowModuleLoaderService> showModuleLoaderService;
readonly Lazy<IMessageBoxService> messageBoxService;
[ImportingConstructor]
ClassLoaderFactoryImpl(UIDispatcher uiDispatcher, Lazy<IDocumentTreeView> documentTreeView, Lazy<IDocumentTabService> documentTabService, Lazy<ShowModuleLoaderService> showModuleLoaderService, Lazy<IMessageBoxService> messageBoxService) {
this.uiDispatcher = uiDispatcher;
this.documentTreeView = documentTreeView;
this.documentTabService = documentTabService;
this.showModuleLoaderService = showModuleLoaderService;
this.messageBoxService = messageBoxService;
}
public override ClassLoader Create(DbgRuntime runtime, DbgDynamicModuleProvider dbgDynamicModuleProvider) =>
new ClassLoaderImpl(uiDispatcher, documentTreeView, documentTabService, showModuleLoaderService, messageBoxService, runtime, dbgDynamicModuleProvider);
}
abstract class ClassLoader {
public abstract void LoadClass(DbgModule module, uint token);
public abstract void LoadNewClasses();
public abstract void LoadEverything_UI(DynamicModuleDefDocument[] documents);
}
sealed class ClassLoaderImpl : ClassLoader {
readonly object lockObj;
readonly UIDispatcher uiDispatcher;
readonly Lazy<IDocumentTreeView> documentTreeView;
readonly Lazy<IDocumentTabService> documentTabService;
readonly Lazy<ShowModuleLoaderService> showModuleLoaderService;
readonly Lazy<IMessageBoxService> messageBoxService;
readonly DbgRuntime runtime;
readonly DbgDynamicModuleProvider dbgDynamicModuleProvider;
readonly Dictionary<DbgModule, HashSet<uint>> loadedClasses;
public ClassLoaderImpl(UIDispatcher uiDispatcher, Lazy<IDocumentTreeView> documentTreeView, Lazy<IDocumentTabService> documentTabService, Lazy<ShowModuleLoaderService> showModuleLoaderService, Lazy<IMessageBoxService> messageBoxService, DbgRuntime runtime, DbgDynamicModuleProvider dbgDynamicModuleProvider) {
lockObj = new object();
this.uiDispatcher = uiDispatcher ?? throw new ArgumentNullException(nameof(uiDispatcher));
this.documentTreeView = documentTreeView ?? throw new ArgumentNullException(nameof(documentTreeView));
this.documentTabService = documentTabService ?? throw new ArgumentNullException(nameof(documentTabService));
this.showModuleLoaderService = showModuleLoaderService ?? throw new ArgumentNullException(nameof(showModuleLoaderService));
this.messageBoxService = messageBoxService ?? throw new ArgumentNullException(nameof(messageBoxService));
this.runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
this.dbgDynamicModuleProvider = dbgDynamicModuleProvider ?? throw new ArgumentNullException(nameof(dbgDynamicModuleProvider));
loadedClasses = new Dictionary<DbgModule, HashSet<uint>>();
}
public override void LoadClass(DbgModule module, uint token) {
lock (lockObj) {
if (!loadedClasses.TryGetValue(module, out var hash))
loadedClasses.Add(module, hash = new HashSet<uint>());
hash.Add(token);
}
}
public override void LoadNewClasses() {
lock (lockObj) {
if (loadedClasses.Count == 0)
return;
}
uiDispatcher.UI(() => LoadNewClasses_UI());
}
void LoadNewClasses_UI() {
uiDispatcher.VerifyAccess();
if (runtime.IsClosed || runtime.Process.State != DbgProcessState.Paused)
return;
Dictionary<DbgModule, HashSet<uint>> oldLoadedClasses;
lock (lockObj) {
if (loadedClasses.Count == 0)
return;
oldLoadedClasses = new Dictionary<DbgModule, HashSet<uint>>(loadedClasses);
loadedClasses.Clear();
}
var visibleDocs = GetVisibleDocuments_UI();
if (visibleDocs.Count == 0)
return;
var states = new List<ModuleState>(visibleDocs.Count);
foreach (var info in visibleDocs) {
HashSet<uint>? hash;
oldLoadedClasses.TryGetValue(info.document.DbgModule, out hash);
states.Add(new ModuleState(info.document, info.documentNode, hash));
}
dbgDynamicModuleProvider.BeginInvoke(() => LoadNewClasses_EngineThread(states));
}
void LoadNewClasses_EngineThread(List<ModuleState> states) {
if (runtime.IsClosed || runtime.Process.State != DbgProcessState.Paused)
return;
foreach (var state in states) {
foreach (var token in dbgDynamicModuleProvider.GetModifiedTypes(state.Document.DbgModule))
state.ModifiedTypes.Add(token);
var nonLoadedTokens = GetNonLoadedTokens(state);
dbgDynamicModuleProvider.InitializeNonLoadedClasses(state.Document.DbgModule, nonLoadedTokens);
}
var remaining = states.Where(a => a.ModifiedTypes.Count != 0 || (a.LoadClassHash is not null && a.LoadClassHash.Count != 0)).Select(a => a.Document).ToArray();
if (remaining.Length == 0)
return;
uiDispatcher.UI(() => {
try {
if (runtime.IsClosed)
return;
LoadEverything_UI(remaining);
foreach (var state in states)
new TreeViewUpdater(documentTabService.Value, state.Document, state.ModuleNode, state.ModifiedTypes, state.LoadClassHash).Update();
}
catch (Exception ex) {
messageBoxService.Value.Show(ex);
}
});
}
uint[] GetNonLoadedTokens(ModuleState state) {
var hash = new HashSet<uint>(state.ModifiedTypes);
if (state.LoadClassHash is not null) {
foreach (var a in state.LoadClassHash)
hash.Add(a);
}
var tokens = hash.ToList();
tokens.Sort();
var res = new List<uint>(tokens.Count);
foreach (uint token in tokens) {
bool loaded = state.LoadClassHash is not null && state.LoadClassHash.Contains(token);
if (loaded)
continue; // It has already been initialized
res.Add(token);
}
return res.ToArray();
}
sealed class ModuleState {
public DynamicModuleDefDocument Document { get; }
public ModuleDocumentNode ModuleNode { get; }
public HashSet<uint>? LoadClassHash { get; }
public HashSet<uint> ModifiedTypes { get; }
public ModuleState(DynamicModuleDefDocument document, ModuleDocumentNode moduleNode, HashSet<uint>? loadClassHash) {
Document = document ?? throw new ArgumentNullException(nameof(document));
ModuleNode = moduleNode ?? throw new ArgumentNullException(nameof(moduleNode));
LoadClassHash = loadClassHash;
ModifiedTypes = new HashSet<uint>();
}
}
List<(DynamicModuleDefDocument document, ModuleDocumentNode documentNode)> GetVisibleDocuments_UI() {
uiDispatcher.VerifyAccess();
var list = new List<(DynamicModuleDefDocument document, ModuleDocumentNode documentNode)>();
foreach (var node in documentTreeView.Value.GetAllModuleNodes()) {
if (node.Document is DynamicModuleDefDocument doc && doc.DbgModule.Runtime == runtime && doc.DbgModule.IsDynamic)
list.Add((doc, node));
}
return list;
}
public override void LoadEverything_UI(DynamicModuleDefDocument[] documents) {
uiDispatcher.VerifyAccess();
if (documents is null)
throw new ArgumentNullException(nameof(documents));
if (runtime.IsClosed)
return;
if (documents.Length == 0)
return;
using (var vm = new ModuleLoaderVM(uiDispatcher, messageBoxService.Value, runtime, dbgDynamicModuleProvider, documents))
showModuleLoaderService.Value.Show(vm);
}
}
}