/*
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();
}
}
}