/* 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.ComponentModel.Composition; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using dnlib.DotNet; using dnlib.DotNet.MD; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet.Metadata; using dnSpy.Contracts.Documents; using dnSpy.Contracts.Documents.Tabs; using dnSpy.Contracts.Documents.TreeView; using dnSpy.Debugger.DotNet.UI; namespace dnSpy.Debugger.DotNet.Metadata { abstract class DbgInMemoryModuleService { public abstract ModuleDef? LoadModule(DbgModule module); public abstract ModuleDef? FindModule(DbgModule module); } [Export(typeof(DbgInMemoryModuleService))] [Export(typeof(IDbgManagerStartListener))] sealed class DbgInMemoryModuleServiceImpl : DbgInMemoryModuleService, IDbgManagerStartListener { readonly object lockObj; readonly UIDispatcher uiDispatcher; readonly Lazy documentTreeView; readonly Lazy documentTabService; readonly Lazy methodAnnotations; readonly DsDocumentProvider documentProvider; readonly DbgAssemblyInfoProviderService dbgAssemblyInfoProviderService; readonly DbgDynamicModuleProviderService dbgDynamicModuleProviderService; readonly ClassLoaderFactory classLoaderFactory; readonly Lazy dbgModuleMemoryRefreshedNotifier; bool UseDebugSymbols => true; IEnumerable AllMemoryModuleDefDocuments => documentProvider.Documents.OfType(); IEnumerable AllDynamicModuleDefDocuments => documentProvider.Documents.OfType(); sealed class RuntimeInfo { readonly DbgInMemoryModuleServiceImpl owner; public DbgAssemblyInfoProvider AssemblyInfoProvider { get; } public DbgDynamicModuleProvider? DynamicModuleProvider { get; } public ClassLoader? ClassLoader { get; } public RuntimeInfo(DbgInMemoryModuleServiceImpl owner, DbgAssemblyInfoProvider dbgAssemblyInfoProvider, DbgDynamicModuleProvider? dbgDynamicModuleProvider, ClassLoader? classLoader) { this.owner = owner ?? throw new ArgumentNullException(nameof(owner)); AssemblyInfoProvider = dbgAssemblyInfoProvider ?? throw new ArgumentNullException(nameof(dbgAssemblyInfoProvider)); DynamicModuleProvider = dbgDynamicModuleProvider; ClassLoader = classLoader; if (dbgDynamicModuleProvider is not null) dbgDynamicModuleProvider.ClassLoaded += DbgDynamicModuleProvider_ClassLoaded; } void DbgDynamicModuleProvider_ClassLoaded(object? sender, ClassLoadedEventArgs e) => owner.DbgDynamicModuleProvider_ClassLoaded(this, e); } [ImportingConstructor] DbgInMemoryModuleServiceImpl(UIDispatcher uiDispatcher, Lazy documentTreeView, Lazy documentTabService, Lazy methodAnnotations, DsDocumentProvider documentProvider, DbgAssemblyInfoProviderService dbgAssemblyInfoProviderService, DbgDynamicModuleProviderService dbgDynamicModuleProviderService, ClassLoaderFactory classLoaderFactory, Lazy dbgModuleMemoryRefreshedNotifier) { lockObj = new object(); this.uiDispatcher = uiDispatcher; this.documentTreeView = documentTreeView; this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; this.documentProvider = documentProvider; this.dbgAssemblyInfoProviderService = dbgAssemblyInfoProviderService; this.dbgDynamicModuleProviderService = dbgDynamicModuleProviderService; this.classLoaderFactory = classLoaderFactory; this.dbgModuleMemoryRefreshedNotifier = dbgModuleMemoryRefreshedNotifier; } void IDbgManagerStartListener.OnStart(DbgManager dbgManager) => dbgManager.ProcessesChanged += DbgManager_ProcessesChanged; void DbgManager_ProcessesChanged(object? sender, DbgCollectionChangedEventArgs e) { if (e.Added) { foreach (var p in e.Objects) { p.RuntimesChanged += DbgProcess_RuntimesChanged; p.IsRunningChanged += DbgProcess_IsRunningChanged; } } else { foreach (var p in e.Objects) { p.RuntimesChanged -= DbgProcess_RuntimesChanged; p.IsRunningChanged -= DbgProcess_IsRunningChanged; } } } void DbgProcess_IsRunningChanged(object? sender, EventArgs e) { var process = (DbgProcess)sender!; if (process.State == DbgProcessState.Paused) { foreach (var r in process.Runtimes) { if (!TryGetRuntimeInfo(r, out var info)) continue; info.ClassLoader?.LoadNewClasses(); } } } void DbgProcess_RuntimesChanged(object? sender, DbgCollectionChangedEventArgs e) { if (e.Added) { foreach (var r in e.Objects) { var assemblyInfoProvider = dbgAssemblyInfoProviderService.Create(r); if (assemblyInfoProvider is null) continue; ClassLoader? classLoader; var dynamicModuleProvider = dbgDynamicModuleProviderService.Create(r); if (dynamicModuleProvider is null) classLoader = null; else classLoader = classLoaderFactory.Create(r, dynamicModuleProvider); r.GetOrCreateData(() => new RuntimeInfo(this, assemblyInfoProvider, dynamicModuleProvider, classLoader)); r.ModulesChanged += DbgRuntime_ModulesChanged; } } else { foreach (var r in e.Objects) r.ModulesChanged -= DbgRuntime_ModulesChanged; } } void DbgDynamicModuleProvider_ClassLoaded(RuntimeInfo info, ClassLoadedEventArgs e) => info.ClassLoader?.LoadClass(e.Module, e.LoadedClassToken); bool TryGetRuntimeInfo(DbgRuntime runtime, [NotNullWhen(true)] out RuntimeInfo? info) => runtime.TryGetData(out info); void DbgRuntime_ModulesChanged(object? sender, DbgCollectionChangedEventArgs e) { if (e.Added) { if (!TryGetRuntimeInfo((DbgRuntime)sender!, out var info)) return; List<(DbgModule manifestModule, DbgModule module)>? list = null; foreach (var module in e.Objects) { var manifestModule = info.AssemblyInfoProvider.GetManifestModule(module); // If it's the manifest module, it can't possibly have been inserted in the treeview if (manifestModule is null || manifestModule == module) continue; if (list is null) list = new List<(DbgModule, DbgModule)>(); list.Add((manifestModule, module)); } if (list is not null) { uiDispatcher.UI(() => { foreach (var t in list) OnModuleAdded_UI(info, t.manifestModule, t.module); }); } } } void OnModuleAdded_UI(RuntimeInfo info, DbgModule manifestModule, DbgModule module) { uiDispatcher.VerifyAccess(); // If an assembly is visible in the treeview, and a new netmodule gets added, add a // new netmodule node to the assembly in the treeview. // Update a dynamic assembly, if one exists if (info.DynamicModuleProvider is not null) { var manifestKey = DynamicModuleDefDocument.CreateKey(manifestModule); var asmFile = FindDocument(manifestKey); if (documentTreeView.Value.FindNode(asmFile) is AssemblyDocumentNode asmNode) { var moduleKey = DynamicModuleDefDocument.CreateKey(module); asmNode.TreeNode.EnsureChildrenLoaded(); Debug.Assert(asmNode.TreeNode.Children.Count >= 1); var moduleNode = asmNode.TreeNode.DataChildren.OfType().FirstOrDefault(a => moduleKey.Equals(a.Document.Key)); Debug2.Assert(moduleNode is null); if (moduleNode is null) { var md = info.DynamicModuleProvider.GetDynamicMetadata(module, out var moduleId); if (md is not null) { UpdateResolver(md); var newFile = new DynamicModuleDefDocument(moduleId, module, md, UseDebugSymbols); asmNode.Document.Children.Add(newFile); Initialize_UI(info, new[] { newFile }); asmNode.TreeNode.Children.Add(documentTreeView.Value.TreeView.Create(documentTreeView.Value.CreateNode(asmNode, newFile))); } } } } // Update an in-memory assembly, if one exists if (manifestModule.HasAddress && module.HasAddress) { var manifestKey = MemoryModuleDefDocument.CreateKey(manifestModule.Process, manifestModule.Address); var asmFile = FindDocument(manifestKey); if (documentTreeView.Value.FindNode(asmFile) is AssemblyDocumentNode asmNode) { var moduleKey = MemoryModuleDefDocument.CreateKey(module.Process, module.Address); asmNode.TreeNode.EnsureChildrenLoaded(); Debug.Assert(asmNode.TreeNode.Children.Count >= 1); var moduleNode = asmNode.TreeNode.DataChildren.OfType().FirstOrDefault(a => moduleKey.Equals(a.Document.Key)); Debug2.Assert(moduleNode is null); if (moduleNode is null) { MemoryModuleDefDocument? newFile = null; try { newFile = MemoryModuleDefDocument.Create(this, module, UseDebugSymbols); } catch { } Debug2.Assert(newFile is not null); if (newFile is not null) { Debug2.Assert(newFile.ModuleDef is not null); UpdateResolver(newFile.ModuleDef); asmNode.Document.Children.Add(newFile); RemoveFromAssembly(newFile.ModuleDef); asmNode.Document.ModuleDef!.Assembly.Modules.Add(newFile.ModuleDef); asmNode.TreeNode.Children.Add(documentTreeView.Value.TreeView.Create(documentTreeView.Value.CreateNode(asmNode, newFile))); } } } } } static void RemoveFromAssembly(ModuleDef module) { // It could be a netmodule that contains an AssemblyDef row, if so remove it from the assembly if (module.Assembly is not null) module.Assembly.Modules.Remove(module); } void UpdateResolver(ModuleDef module) { if (module is not null) module.Context = DsDotNetDocumentBase.CreateModuleContext(documentProvider.AssemblyResolver); } IDsDocument? FindDocument(IDsDocumentNameKey key) => documentProvider.Find(key); public override ModuleDef? LoadModule(DbgModule module) { if (module is null) throw new ArgumentNullException(nameof(module)); if (module.IsDynamic) return LoadDynamicModule(module); return LoadMemoryModule(module); } ModuleDef? LoadDynamicModule(DbgModule module) { if (module is null) throw new ArgumentNullException(nameof(module)); var doc = FindDynamicModule(module); if (doc is not null) return doc; if (!TryGetRuntimeInfo(module.Runtime, out var info)) return null; if (info.DynamicModuleProvider is null) return null; if (module.Process.State != DbgProcessState.Paused) return null; // Can happen if the breakpoints window just opened and a dynamic assembly is requested. if (uiDispatcher.IsProcessingDisabled()) return null; lock (lockObj) { doc = FindDynamicModule(module); if (doc is not null) return doc; var modules = info.AssemblyInfoProvider.GetAssemblyModules(module); if (modules.Length == 0) return null; var manifestDnModule = modules[0]; var manifestKey = DynamicModuleDefDocument.CreateKey(manifestDnModule); var manMod = FindDocument(manifestKey); Debug2.Assert(manMod is null); if (manMod is not null) return null; var manDoc = FindDynamicModule(manifestDnModule); Debug2.Assert(manDoc is null); if (manDoc is not null) return null; var files = new List(modules.Length); DynamicModuleDefDocument? resDoc = null; foreach (var m in modules) { var md = info.DynamicModuleProvider.GetDynamicMetadata(m, out var moduleId); if (md is null) continue; UpdateResolver(md); var newDoc = new DynamicModuleDefDocument(moduleId, m, md, UseDebugSymbols); if (m == module) resDoc = newDoc; files.Add(newDoc); } if (files.Count == 0) return null; Initialize(info, files.ToArray()); var asmFile = DynamicModuleDefDocument.CreateAssembly(files); var addedFile = documentProvider.GetOrAdd(asmFile); Debug.Assert(addedFile == asmFile); return resDoc?.ModuleDef; } } ModuleDef? LoadMemoryModule(DbgModule module) { if (module is null) throw new ArgumentNullException(nameof(module)); if (!TryGetRuntimeInfo(module.Runtime, out var info)) return null; Debug.Assert(!module.IsDynamic); if (!module.HasAddress) return null; var doc = FindMemoryModule(module); if (doc is not null) return doc; MemoryModuleDefDocument? result = null; lock (lockObj) { doc = FindMemoryModule(module); if (doc is not null) return doc; var modules = info.AssemblyInfoProvider.GetAssemblyModules(module); if (modules.Length == 0) return null; var manifestModule = modules[0]; var manifestKey = MemoryModuleDefDocument.CreateKey(manifestModule.Process, manifestModule.Address); var manMod = FindDocument(manifestKey); Debug2.Assert(manMod is null); if (manMod is not null) return null; var manDoc = FindMemoryModule(manifestModule); Debug2.Assert(manDoc is null); if (manDoc is not null) return null; var docs = new List(modules.Length); foreach (var m in modules) { MemoryModuleDefDocument modDoc; try { modDoc = MemoryModuleDefDocument.Create(this, m, UseDebugSymbols); UpdateResolver(modDoc.ModuleDef!); if (m == module) result = modDoc; } catch { // The PE headers and/or .NET headers are probably corrupt return LoadDynamicModule(module); } docs.Add(modDoc); } Debug2.Assert(result is not null); if (docs.Count == 0 || result is null) return null; var asmFile = MemoryModuleDefDocument.CreateAssembly(docs); var asm = docs[0].AssemblyDef; if (asm is null) { if (docs.Count > 1) { asm = docs[0].ModuleDef!.UpdateRowId(new AssemblyDefUser("???")); asm.Modules.Add(docs[0].ModuleDef); } } asm?.Modules.Clear(); for (int i = 0; i < docs.Count; i++) { RemoveFromAssembly(docs[i].ModuleDef!); asm!.Modules.Add(docs[i].ModuleDef); } var addedFile = documentProvider.GetOrAdd(asmFile); Debug.Assert(addedFile == asmFile); } // The modules got loaded for the first time, but it's possible that the debugger is using the // old disk file modules. Raise an event so the debugger rereads the memory. var newModules = GetModules(result.Process, result.Address); if (newModules.Length > 0) dbgModuleMemoryRefreshedNotifier.Value.RaiseModulesRefreshed(newModules); return result.ModuleDef; } public override ModuleDef? FindModule(DbgModule module) { if (module is null) throw new ArgumentNullException(nameof(module)); if (module.IsDynamic) return FindDynamicModule(module); // It could be a dynamic module if LoadMemoryModule() failed and called LoadDynamicModule() return FindMemoryModule(module) ?? FindDynamicModule(module); } ModuleDef? FindDynamicModule(DbgModule module) { if (module is null) throw new ArgumentNullException(nameof(module)); return AllDynamicModuleDefDocuments.FirstOrDefault(a => a.DbgModule == module)?.ModuleDef; } ModuleDef? FindMemoryModule(DbgModule module) { if (module is null) throw new ArgumentNullException(nameof(module)); if (!module.HasAddress) return null; var key = MemoryModuleDefDocument.CreateKey(module.Process, module.Address); return AllMemoryModuleDefDocuments.FirstOrDefault(a => key.Equals(a.Key))?.ModuleDef; } void Initialize(RuntimeInfo info, DynamicModuleDefDocument[] docs) => uiDispatcher.Invoke(() => Initialize_UI(info, docs)); void Initialize_UI(RuntimeInfo info, DynamicModuleDefDocument[] docs) { uiDispatcher.VerifyAccess(); Debug2.Assert(info.DynamicModuleProvider is not null); if (info.DynamicModuleProvider is null) return; info.ClassLoader?.LoadEverything_UI(docs); } internal void UpdateModuleMemory(MemoryModuleDefDocument document) { uiDispatcher.VerifyAccess(); if (document.TryUpdateMemory()) RefreshBodies(document); // Always reset all breakpoints. If we set breakpoints (and fail) and later the module // gets decrypted and we then open the in-memory copy, TryUpdateMemory() will return // false ("no changes"), but the breakpoints will still need to be refreshed. var modules = GetModules(document.Process, document.Address); if (modules.Length > 0) dbgModuleMemoryRefreshedNotifier.Value.RaiseModulesRefreshed(modules); } void RefreshBodies(MemoryModuleDefDocument document) { uiDispatcher.VerifyAccess(); if (document.ModuleDef!.EnableTypeDefFindCache) { document.ModuleDef.EnableTypeDefFindCache = false; document.ModuleDef.EnableTypeDefFindCache = true; } // Free all method bodies and clear cache so the new bodies are shown if any // got modified (eg. decrypted in memory) for (uint rid = 1; ; rid++) { var md = document.ModuleDef.ResolveToken(new MDToken(Table.Method, rid)) as MethodDef; if (md is null) break; methodAnnotations.Value.SetBodyModified(md, false); md.FreeMethodBody(); } documentTabService.Value.RefreshModifiedDocument(document); } static DbgModule[] GetModules(DbgProcess process, ulong address) => process.Runtimes.SelectMany(a => a.Modules).Where(a => a.HasAddress && a.Address == address).ToArray(); } }