/* 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 dnlib.DotNet; using dnlib.PE; using dnSpy.Contracts.Utilities; namespace dnSpy.Contracts.Documents { /// /// Document base class /// public abstract class DsDocument : IDsDocument2 { /// public abstract DsDocumentInfo? SerializedDocument { get; } /// public abstract IDsDocumentNameKey Key { get; } /// public AssemblyDef? AssemblyDef => ModuleDef?.Assembly; /// public virtual ModuleDef? ModuleDef => null; /// public virtual IPEImage? PEImage => (ModuleDef as ModuleDefMD)?.Metadata?.PEImage; /// public string Filename { get => filename; set { if (filename != value) { filename = value; OnPropertyChanged(nameof(Filename)); } } } string filename = string.Empty; /// /// Gets called when a property has changed /// /// Name of property protected virtual void OnPropertyChanged(string propName) { } /// public bool IsAutoLoaded { get; set; } /// public TList Children { get { if (children is null) { lock (lockObj) { if (children is null) { children = CreateChildren(); Debug2.Assert(children is not null); if (children is null) children = new TList(); } } } return children; } } readonly object lockObj; TList? children; /// public bool ChildrenLoaded => children is not null; /// /// Creates the children /// /// protected virtual TList CreateChildren() => new TList(); /// /// Constructor /// protected DsDocument() => lockObj = new object(); /// public T? AddAnnotation(T? annotation) where T : class => annotations.AddAnnotation(annotation); /// public T? Annotation() where T : class => annotations.Annotation(); /// public IEnumerable Annotations() where T : class => annotations.Annotations(); /// public void RemoveAnnotations() where T : class => annotations.RemoveAnnotations(); readonly AnnotationsImpl annotations = new AnnotationsImpl(); /// public virtual void OnAdded() { } } /// /// Unknown type of file /// public sealed class DsUnknownDocument : DsDocument { /// public override DsDocumentInfo? SerializedDocument => DsDocumentInfo.CreateDocument(Filename); /// public override IDsDocumentNameKey Key => new FilenameKey(Filename); /// /// Constructor /// /// Filename public DsUnknownDocument(string filename) => Filename = filename ?? string.Empty; } /// /// PE file /// public sealed class DsPEDocument : DsDocument, IDsPEDocument, IDisposable { /// public override DsDocumentInfo? SerializedDocument => DsDocumentInfo.CreateDocument(Filename); /// public override IDsDocumentNameKey Key => FilenameKey.CreateFullPath(Filename); /// public override IPEImage? PEImage { get; } /// /// Constructor /// /// PE image public DsPEDocument(IPEImage peImage) { PEImage = peImage; Filename = peImage.Filename ?? string.Empty; } /// public void Dispose() => PEImage!.Dispose(); } /// /// .NET file base class /// public abstract class DsDotNetDocumentBase : DsDocument, IDsDotNetDocument, IInMemoryDocument { /// public override ModuleDef? ModuleDef { get; } /// public virtual bool IsActive => true; /// true if the symbols have been loaded protected bool loadedSymbols; /// /// Constructor /// /// Module /// true if symbols should be loaded protected DsDotNetDocumentBase(ModuleDef module, bool loadSyms) { ModuleDef = module; loadedSymbols = loadSyms; Filename = module.Location ?? string.Empty; module.EnableTypeDefFindCache = true; } /// public override void OnAdded() { if (loadedSymbols) LoadSymbols(); base.OnAdded(); } /// /// Creates a module context /// /// Assembly resolver /// public static ModuleContext CreateModuleContext(IAssemblyResolver asmResolver) { var moduleCtx = new ModuleContext(); moduleCtx.AssemblyResolver = asmResolver; // Disable WinMD projection since the user probably expects that clicking on a type // will take you to that type, and not to the projected CLR type. // The decompiler shouldn't have a problem with this since it uses SigComparer() which // defaults to projecting WinMD types. moduleCtx.Resolver = new Resolver(moduleCtx.AssemblyResolver) { ProjectWinMDRefs = false }; return moduleCtx; } void LoadSymbols() { Debug2.Assert(ModuleDef is not null); // Happens if a module has been removed but then the exact same instance // was re-added. if (ModuleDef.PdbState is not null) return; var m = ModuleDef as ModuleDefMD; if (m is null) return; try { m.LoadPdb(); } catch { } } } /// /// .NET file /// public class DsDotNetDocument : DsDotNetDocumentBase, IDisposable { readonly bool isAsmNode; /// public override IDsDocumentNameKey Key => FilenameKey.CreateFullPath(Filename); /// public override DsDocumentInfo? SerializedDocument => documentInfo; DsDocumentInfo documentInfo; /// /// Constructor /// /// Document info /// Module /// true to load symbols /// true if it's an assembly node, false if it's a module node protected DsDotNetDocument(DsDocumentInfo documentInfo, ModuleDef module, bool loadSyms, bool isAsmNode) : base(module, loadSyms) { this.documentInfo = documentInfo; this.isAsmNode = isAsmNode; } /// protected override void OnPropertyChanged(string propName) { base.OnPropertyChanged(propName); if (propName == nameof(Filename)) documentInfo = DsDocumentInfo.CreateDocument(Filename); } /// /// Creates an assembly /// /// Document info /// Module /// true to load symbols /// public static DsDotNetDocument CreateAssembly(DsDocumentInfo documentInfo, ModuleDef module, bool loadSyms) => new DsDotNetDocument(documentInfo, module, loadSyms, true); /// /// Creates a module /// /// Document info /// Module /// true to load symbols /// public static DsDotNetDocument CreateModule(DsDocumentInfo documentInfo, ModuleDef module, bool loadSyms) => new DsDotNetDocument(documentInfo, module, loadSyms, false); /// /// Creates an assembly /// /// Module /// public static DsDotNetDocument CreateAssembly(IDsDotNetDocument module) => new DsDotNetDocumentAsmWithMod(module); /// protected override TList CreateChildren() { var asm = AssemblyDef; var list = new TList(asm is null ? 1 : asm.Modules.Count); if (isAsmNode && asm is not null) { bool foundThis = false; foreach (var module in asm.Modules) { if (ModuleDef == module) { Debug.Assert(!foundThis); foundThis = true; } list.Add(new DsDotNetDocument(DsDocumentInfo.CreateDocument(module.Location), module, loadedSymbols, false)); } Debug.Assert(foundThis); } return list; } /// public void Dispose() => ModuleDef!.Dispose(); } sealed class DsDotNetDocumentAsmWithMod : DsDotNetDocument { IDsDotNetDocument? module; public DsDotNetDocumentAsmWithMod(IDsDotNetDocument modmodule) : base(modmodule.SerializedDocument ?? new DsDocumentInfo(), modmodule.ModuleDef!, false, true) => module = modmodule; protected override TList CreateChildren() { Debug2.Assert(module is not null); var list = new TList(); if (module is not null) list.Add(module); module = null; return list; } } /// /// mmap'd I/O helper methods /// static class MemoryMappedIOHelper { /// /// Disable memory mapped I/O /// /// Document public static void DisableMemoryMappedIO(IDsDocument document) { if (document is null) return; DisableMemoryMappedIO(document.PEImage); } /// /// Disable memory mapped I/O /// /// PE image public static void DisableMemoryMappedIO(IPEImage? peImage) { if (peImage is null) return; // Files in the GAC are read-only so there's no need to disable memory mapped I/O to // allow other programs to write to the file. if (GacInfo.IsGacPath(peImage.Filename)) return; (peImage as IInternalPEImage)?.UnsafeDisableMemoryMappedIO(); } } }