/* 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.IO; using System.Linq; using dnlib.DotNet.MD; using dnlib.PE; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.CallStack; using dnSpy.Contracts.Debugger.DotNet.Evaluation; using dnSpy.Contracts.Debugger.DotNet.Evaluation.ExpressionCompiler; using dnSpy.Contracts.Debugger.DotNet.Metadata.Internal; using dnSpy.Debugger.DotNet.Metadata; using dnSpy.Debugger.DotNet.Properties; namespace dnSpy.Debugger.DotNet.Evaluation.Engine { abstract class DbgModuleReferenceProvider { /// /// Gets the module references or an empty array if is an unsupported frame with no .NET module /// /// Runtime /// Frame /// Extra references /// public abstract GetModuleReferencesResult GetModuleReferences(DbgRuntime runtime, DbgStackFrame frame, DmdType[] typeReferences); public abstract GetModuleReferencesResult GetModuleReferences(DbgRuntime runtime, DmdModule module, DmdType[] typeReferences); } readonly struct GetModuleReferencesResult { public DbgModuleReference[]? ModuleReferences { get; } public string? ErrorMessage { get; } public GetModuleReferencesResult(string errorMessage) { ModuleReferences = null; ErrorMessage = errorMessage ?? throw new ArgumentNullException(nameof(errorMessage)); } public GetModuleReferencesResult(DbgModuleReference[] moduleReferences) { ModuleReferences = moduleReferences ?? throw new ArgumentNullException(nameof(moduleReferences)); ErrorMessage = null; } } [Export(typeof(DbgModuleReferenceProvider))] sealed class DbgModuleReferenceProviderImpl : DbgModuleReferenceProvider { readonly DbgRawMetadataService dbgRawMetadataService; [ImportingConstructor] DbgModuleReferenceProviderImpl(DbgRawMetadataService dbgRawMetadataService) => this.dbgRawMetadataService = dbgRawMetadataService; sealed class RuntimeState : IDisposable { public readonly struct Key : IEquatable { public readonly bool IsFileLayout; public readonly ulong Address; public readonly uint Size; public Key(bool isFileLayout, ulong address, uint size) { IsFileLayout = isFileLayout; Address = address; Size = size; } public bool Equals(Key other) => Address == other.Address && Size == other.Size && IsFileLayout == other.IsFileLayout; public override bool Equals(object? obj) => obj is Key other && Equals(other); public override int GetHashCode() => Address.GetHashCode(); } public readonly Dictionary ModuleReferences = new Dictionary(); public readonly List OtherModuleReferences = new List(); readonly DbgRuntime runtime; RuntimeState(DbgRuntime runtime) => this.runtime = runtime; public static RuntimeState GetRuntimeState(DbgRuntime runtime) { if (runtime.TryGetData(out RuntimeState? state)) return state; return CreateRuntimeState(runtime); } static RuntimeState CreateRuntimeState(DbgRuntime runtime) => runtime.GetOrCreateData(() => new RuntimeState(runtime)); public void Dispose() { var refs = ModuleReferences.Values.ToArray(); ModuleReferences.Clear(); runtime.Process.DbgManager.Close(refs); refs = OtherModuleReferences.ToArray(); OtherModuleReferences.Clear(); runtime.Process.DbgManager.Close(refs); } } sealed class ModuleReferencesState { public DbgModuleReference[]? ModuleReferences; /// /// Module reference of source module /// public DbgModuleReference? SourceModuleReference; /// /// Referenced assemblies that have been loaded, including all their current modules. If an /// assembly gets unloaded or if it loads/unloads a module, we need to invalidate the cached data. /// public readonly List AssemblyInfos = new List(); /// /// Referenced assemblies that haven't been loaded yet. If one of these get loaded, /// we need to invalidate the cached data. /// public readonly HashSet NonLoadedAssemblies = new HashSet(DmdMemberInfoEqualityComparer.DefaultMember); /// /// Extra type references (from object ids, $exception and other aliases) /// public DmdType[]? TypeReferences; } readonly struct AssemblyInfo { public readonly DmdAssembly Assembly; public readonly ModuleInfo[] Modules; public AssemblyInfo(DmdAssembly assembly) { Assembly = assembly; var modules = assembly.GetModules(); var infos = new ModuleInfo[modules.Length]; for (int i = 0; i < infos.Length; i++) infos[i] = new ModuleInfo(modules[i]); Modules = infos; } } readonly struct ModuleInfo { public readonly DmdModule Module; public readonly DbgModule? DebuggerModule; public readonly int DynamicModuleVersion; public readonly int DebuggerModuleVersion; public ModuleInfo(DmdModule module) { Module = module; DebuggerModule = module.GetDebuggerModule(); DynamicModuleVersion = module.DynamicModuleVersion; DebuggerModuleVersion = DebuggerModule?.RefreshedVersion ?? -1; } } sealed class DbgModuleReferenceImpl : DbgModuleReference { public override IntPtr MetadataAddress => dbgRawMetadata.MetadataAddress; public override uint MetadataSize => (uint)dbgRawMetadata.MetadataSize; public override Guid ModuleVersionId { get; } public override Guid GenerationId => generationId; readonly DbgRawMetadata dbgRawMetadata; Guid generationId; int refreshedVersion; #if DEBUG readonly string toStringValue; #endif public DbgModuleReferenceImpl(DbgRawMetadata dbgRawMetadata, Guid moduleVersionId, DmdModule moduleForToString, int refreshedVersion) { PatchMetadata(dbgRawMetadata); this.dbgRawMetadata = dbgRawMetadata; ModuleVersionId = moduleVersionId; this.refreshedVersion = refreshedVersion; #if DEBUG toStringValue = $"{moduleForToString.Assembly.FullName} [{moduleForToString.FullyQualifiedName}]"; #endif } unsafe static void PatchMetadata(DbgRawMetadata rawMd) { try { using (var peImage = new PEImage(rawMd.Address, (uint)rawMd.Size, rawMd.IsFileLayout ? ImageLayout.File : ImageLayout.Memory, true)) { using (var md = MetadataFactory.CreateMetadata(peImage)) new MetadataFixer(md, (void*)rawMd.Address).Fix(); } } catch (BadImageFormatException) { } catch (IOException) { } } internal void Update(int version) { if (refreshedVersion != version) { refreshedVersion = version; generationId = Guid.NewGuid(); dbgRawMetadata.UpdateMemory(); PatchMetadata(dbgRawMetadata); } } protected override void CloseCore(DbgDispatcher dispatcher) => dbgRawMetadata.Release(); #if DEBUG public override string ToString() => toStringValue; #endif } public override GetModuleReferencesResult GetModuleReferences(DbgRuntime runtime, DbgStackFrame frame, DmdType[] typeReferences) { var reflectionModule = frame.Module?.GetReflectionModule(); if (reflectionModule is null) return new GetModuleReferencesResult(dnSpy_Debugger_DotNet_Resources.CantEvaluateWhenCurrentFrameIsNative); return GetModuleReferences(runtime, reflectionModule, typeReferences); } public override GetModuleReferencesResult GetModuleReferences(DbgRuntime runtime, DmdModule module, DmdType[] typeReferences) { // Not thread safe since all callers should call it on the correct engine thread runtime.GetDotNetRuntime().Dispatcher.VerifyAccess(); if (module.TryGetData(out ModuleReferencesState? state)) { if (CanReuse(module.AppDomain, typeReferences, state)) return CreateGetModuleReferencesResult(state); } else state = module.GetOrCreateData(); InitializeState(runtime, module, typeReferences, state); return CreateGetModuleReferencesResult(state); } GetModuleReferencesResult CreateGetModuleReferencesResult(ModuleReferencesState state) { if (state.SourceModuleReference is null || state.SourceModuleReference.MetadataAddress == IntPtr.Zero || state.SourceModuleReference.MetadataSize == 0) return new GetModuleReferencesResult(dnSpy_Debugger_DotNet_Resources.ModuleMetadataNotFoundOrInvalid); return new GetModuleReferencesResult(state.ModuleReferences!); } sealed class IntrinsicsAssemblyState { public readonly DbgModuleReference ModuleReference; public readonly DmdAssembly Assembly; public IntrinsicsAssemblyState(DbgModuleReference moduleReference, DmdAssembly assembly) { ModuleReference = moduleReference ?? throw new ArgumentNullException(nameof(moduleReference)); Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); } } IntrinsicsAssemblyState GetIntrinsicsAssemblyState(DbgRuntime runtime, DmdAppDomain appDomain) { if (appDomain.TryGetData(out IntrinsicsAssemblyState? state)) return state; return GetOrCreateIntrinsicsAssemblyState(runtime, appDomain); } IntrinsicsAssemblyState GetOrCreateIntrinsicsAssemblyState(DbgRuntime runtime, DmdAppDomain appDomain) { var info = new IntrinsicsAssemblyBuilder(appDomain.CorLib!.GetName().FullName, appDomain.CorLib.ImageRuntimeVersion).Create(); const bool isFileLayout = true; const bool isInMemory = false; const bool isDynamic = false; var assembly = appDomain.CreateSyntheticAssembly(() => new DmdLazyMetadataBytesArray(info.assemblyBytes, isFileLayout), isInMemory, isDynamic, DmdModule.GetFullyQualifiedName(isInMemory, isDynamic, null), string.Empty, info.assemblySimpleName); var rawMD = dbgRawMetadataService.Create(runtime, isFileLayout, info.assemblyBytes); var modRef = new DbgModuleReferenceImpl(rawMD, assembly.ManifestModule.ModuleVersionId, assembly.ManifestModule, -1); RuntimeState.GetRuntimeState(runtime).OtherModuleReferences.Add(modRef); var state = new IntrinsicsAssemblyState(modRef, assembly); return appDomain.GetOrCreateData(() => { appDomain.Add(state.Assembly); return state; }); } void InitializeState(DbgRuntime runtime, DmdModule sourceModule, DmdType[] typeReferences, ModuleReferencesState state) { state.AssemblyInfos.Clear(); state.NonLoadedAssemblies.Clear(); state.SourceModuleReference = null; state.TypeReferences = typeReferences; var assembly = sourceModule.Assembly; var appDomain = assembly.AppDomain; var intrinsicsState = GetIntrinsicsAssemblyState(runtime, appDomain); var hash = new HashSet(); var stack = new List(); if (typeReferences.Length != 0) { var finder = ModuleRefFinder.Create(); foreach (var type in typeReferences) finder.Add(type); foreach (var module in finder.GetModules()) stack.Add(new AssemblyInfo(module.Assembly)); } stack.Add(new AssemblyInfo(assembly)); stack.Add(new AssemblyInfo(intrinsicsState.Assembly)); stack.Add(new AssemblyInfo(appDomain.CorLib!)); while (stack.Count > 0) { var info = stack[stack.Count - 1]; stack.RemoveAt(stack.Count - 1); if (!hash.Add(info.Assembly)) continue; state.AssemblyInfos.Add(info); foreach (var modInfo in info.Modules) { foreach (var asmRef in modInfo.Module.GetReferencedAssemblies()) { var asm = appDomain.GetAssembly(asmRef); if (asm is not null) { if (!hash.Contains(asm)) stack.Add(new AssemblyInfo(asm)); } else state.NonLoadedAssemblies.Add(asmRef); } } } var rtState = RuntimeState.GetRuntimeState(runtime); var modRefs = new List(); var dnRuntime = runtime.GetDotNetRuntime(); foreach (var asmInfo in state.AssemblyInfos) { foreach (var modInfo in asmInfo.Modules) { DbgModuleReference? modRef; if (modInfo.Module.Assembly == intrinsicsState.Assembly) modRef = intrinsicsState.ModuleReference; else modRef = GetOrCreateModuleReference(rtState, runtime, modInfo); if (modRef is null) continue; modRefs.Add(modRef); if (modInfo.Module == sourceModule) { Debug2.Assert(state.SourceModuleReference is null); state.SourceModuleReference = modRef; } } } // One of the extra type references might not have added a new module reference. // If so, re-use the same array. Callers compare it by reference to see if // module references have changed. if (!Equals(state.ModuleReferences, modRefs)) state.ModuleReferences = modRefs.ToArray(); } static bool Equals(DbgModuleReference[]? a, List b) { if (a is null) return false; if (a.Length != b.Count) return false; for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) return false; } return true; } DbgModuleReference? GetOrCreateModuleReference(RuntimeState rtState, DbgRuntime runtime, in ModuleInfo modInfo) { DbgModuleReferenceImpl? modRef; var module = modInfo.DebuggerModule ?? modInfo.Module.GetDebuggerModule(); if (module?.HasAddress == true) { var key = new RuntimeState.Key(module.ImageLayout == DbgImageLayout.File, module.Address, module.Size); if (!rtState.ModuleReferences.TryGetValue(key, out modRef)) { var rawMd = dbgRawMetadataService.Create(runtime, key.IsFileLayout, key.Address, (int)key.Size); if (rawMd.MetadataAddress == IntPtr.Zero) { rawMd.Release(); modRef = null; } else { try { modRef = new DbgModuleReferenceImpl(rawMd, modInfo.Module.ModuleVersionId, modInfo.Module, module.RefreshedVersion); rtState.ModuleReferences.Add(key, modRef); } catch { rawMd.Release(); throw; } } } if (modRef is not null) { modRef.Update(module.RefreshedVersion); return modRef; } } if (module is not null && !module.IsDynamic && !module.IsInMemory && File.Exists(module.Filename)) { modRef = GetOrCreateFileModuleReference(rtState, runtime, modInfo, module); if (modRef is not null) return modRef; } if (module is not null) { var info = runtime.GetDotNetRuntime().GetRawModuleBytes(module); if (info.RawBytes is not null) { var state = module.GetOrCreateData(); if (state.Equals(info)) return state.ModuleReference; var rawMd = dbgRawMetadataService.Create(runtime, info.IsFileLayout, info.RawBytes); if (rawMd.MetadataAddress == IntPtr.Zero) rawMd.Release(); else { try { modRef = new DbgModuleReferenceImpl(rawMd, modInfo.Module.ModuleVersionId, modInfo.Module, module.RefreshedVersion); rtState.OtherModuleReferences.Add(modRef); state.RawModuleBytes = info; state.ModuleReference = modRef; return modRef; } catch { rawMd.Release(); throw; } } } } return null; } sealed class RawModuleBytesModuleState { public DbgDotNetRawModuleBytes RawModuleBytes; public DbgModuleReferenceImpl? ModuleReference; public bool Equals(in DbgDotNetRawModuleBytes other) => RawModuleBytes.RawBytes == other.RawBytes && RawModuleBytes.IsFileLayout == other.IsFileLayout; } sealed class FileModuleReferenceState { public readonly DbgModuleReferenceImpl ModuleReference; public FileModuleReferenceState(DbgModuleReferenceImpl moduleReference) => ModuleReference = moduleReference ?? throw new ArgumentNullException(nameof(moduleReference)); } DbgModuleReferenceImpl? GetOrCreateFileModuleReference(RuntimeState rtState, DbgRuntime runtime, in ModuleInfo modInfo, DbgModule module) { Debug.Assert(!module.IsInMemory && File.Exists(module.Filename)); if (module.TryGetData(out var state)) return state.ModuleReference; DbgModuleReferenceImpl modRef; try { var moduleBytes = File.ReadAllBytes(module.Filename); var rawMd = dbgRawMetadataService.Create(runtime, isFileLayout: true, moduleBytes: moduleBytes); if (rawMd.MetadataAddress == IntPtr.Zero) { rawMd.Release(); return null; } else { try { modRef = new DbgModuleReferenceImpl(rawMd, modInfo.Module.ModuleVersionId, modInfo.Module, module.RefreshedVersion); state = module.GetOrCreateData(() => new FileModuleReferenceState(modRef)); rtState.OtherModuleReferences.Add(modRef); return state.ModuleReference; } catch { rawMd.Release(); throw; } } } catch { return null; } } bool CanReuse(DmdAppDomain appDomain, DmdType[] typeReferences, ModuleReferencesState state) { if (state.TypeReferences!.Length != typeReferences.Length) return false; for (int i = 0; i < typeReferences.Length; i++) { if ((object)typeReferences[i] != state.TypeReferences[i]) return false; } foreach (var asmRef in state.NonLoadedAssemblies) { if (appDomain.GetAssembly(asmRef) is not null) return false; } foreach (var info in state.AssemblyInfos) { if (!info.Assembly.IsLoaded) return false; var modules = info.Assembly.GetModules(); if (!Equals(modules, info.Modules)) return false; } return true; } static bool Equals(DmdModule[] a, ModuleInfo[] b) { if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { ref readonly var info = ref b[i]; var am = a[i]; if (am != info.Module || am.DynamicModuleVersion != info.DynamicModuleVersion) return false; var dm = info.DebuggerModule ?? am.GetDebuggerModule(); if (dm is not null && dm.RefreshedVersion != info.DebuggerModuleVersion) return false; } return true; } } }