/* 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.Diagnostics; using System.IO; using dndbg.COM.CorDebug; using dndbg.COM.MetaData; using dndbg.Engine; using dnlib.DotNet; using dnlib.DotNet.MD; using dnlib.PE; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.Engine; using dnSpy.Debugger.DotNet.Metadata; namespace dnSpy.Debugger.DotNet.CorDebug.Impl { static class ModuleCreator { public static DbgEngineModule CreateModule(DbgEngineImpl engine, DbgObjectFactory objectFactory, DbgAppDomain? appDomain, DnModule dnModule, T data) where T : class { ulong address = dnModule.Address; uint size = dnModule.Size; var imageLayout = CalculateImageLayout(dnModule); string name = GetFilename(dnModule.Name); string filename = dnModule.Name; bool isDynamic = dnModule.IsDynamic; bool isInMemory = dnModule.IsInMemory; bool? isOptimized = CalculateIsOptimized(dnModule); int order = dnModule.UniqueId + 1;// 0-based to 1-based InitializeExeFields(dnModule, filename, imageLayout, out var isExe, out var isDll, out var timestamp, out var version, out var assemblySimpleName); if (appDomain is null) throw new InvalidOperationException("No appdomain"); var reflectionAppDomain = ((DbgCorDebugInternalAppDomainImpl)appDomain.InternalAppDomain).ReflectionAppDomain; var closedListenerCollection = new ClosedListenerCollection(); var modules = dnModule.Assembly.Modules; bool isManifestModule = modules[0] == dnModule; var getMetadata = CreateGetMetadataDelegate(engine, objectFactory.Runtime, dnModule, closedListenerCollection, imageLayout); var fullyQualifiedName = DmdModule.GetFullyQualifiedName(isInMemory, isDynamic, dnModule.Name); DmdAssembly reflectionAssembly; DmdModule reflectionModule; if (isManifestModule) { var assemblyLocation = isInMemory || isDynamic ? string.Empty : dnModule.Name; var asmOptions = DmdCreateAssemblyOptions.None; if (isInMemory) asmOptions |= DmdCreateAssemblyOptions.InMemory; if (isDynamic) asmOptions |= DmdCreateAssemblyOptions.Dynamic; if (isExe) asmOptions |= DmdCreateAssemblyOptions.IsEXE; else if (isDll) asmOptions |= DmdCreateAssemblyOptions.IsDLL; var asmInfo = new DmdCreateAssemblyInfo(asmOptions, fullyQualifiedName, assemblyLocation, assemblySimpleName); reflectionAssembly = reflectionAppDomain.CreateAssembly(getMetadata, asmInfo); reflectionModule = reflectionAssembly.ManifestModule; } else { var manifestModule = engine.TryGetModule(modules[0].CorModule); if (manifestModule is null) throw new InvalidOperationException(); reflectionAssembly = ((DbgCorDebugInternalModuleImpl)manifestModule.InternalModule).ReflectionModule!.Assembly; reflectionModule = reflectionAppDomain.CreateModule(reflectionAssembly, getMetadata, isInMemory, isDynamic, fullyQualifiedName); } var internalModule = new DbgCorDebugInternalModuleImpl(reflectionModule, closedListenerCollection); return objectFactory.CreateModule(appDomain, internalModule, isExe, address, size, imageLayout, name, filename, isDynamic, isInMemory, isOptimized, order, timestamp, version, engine.GetMessageFlags(), data: data, onCreated: engineModule => internalModule.SetModule(engineModule.Module)); } static Func CreateGetMetadataDelegate(DbgEngineImpl engine, DbgRuntime runtime, DnModule dnModule, ClosedListenerCollection closedListenerCollection, DbgImageLayout imageLayout) { if (dnModule.IsDynamic) return CreateDynamicGetMetadataDelegate(engine, dnModule); else return CreateNormalGetMetadataDelegate(engine, runtime, dnModule, closedListenerCollection, imageLayout); } static Func CreateDynamicGetMetadataDelegate(DbgEngineImpl engine, DnModule dnModule) { Debug.Assert(dnModule.IsDynamic); var comMetadata = dnModule.CorModule.GetMetaDataInterface(); if (comMetadata is null) throw new InvalidOperationException(); var result = new DmdLazyMetadataBytesCom(comMetadata, engine.GetDynamicModuleHelper(dnModule), engine.DmdDispatcher); return () => result; } static Func CreateNormalGetMetadataDelegate(DbgEngineImpl engine, DbgRuntime runtime, DnModule dnModule, ClosedListenerCollection closedListenerCollection, DbgImageLayout imageLayout) { Debug.Assert(!dnModule.IsDynamic); return () => { var rawMd = engine.RawMetadataService.Create(runtime, imageLayout == DbgImageLayout.File, dnModule.Address, (int)dnModule.Size); try { closedListenerCollection.Closed += (s, e) => rawMd.Release(); return new DmdLazyMetadataBytesPtr(rawMd.Address, (uint)rawMd.Size, rawMd.IsFileLayout); } catch { rawMd.Release(); throw; } }; } static DbgImageLayout CalculateImageLayout(DnModule dnModule) { if (dnModule.IsDynamic) return DbgImageLayout.Unknown; if (dnModule.IsInMemory) return DbgImageLayout.File; return DbgImageLayout.Memory; } static bool? CalculateIsOptimized(DnModule dnModule) { switch (dnModule.CachedJITCompilerFlags) { case CorDebugJITCompilerFlags.CORDEBUG_JIT_DEFAULT: return true; case CorDebugJITCompilerFlags.CORDEBUG_JIT_DISABLE_OPTIMIZATION: case CorDebugJITCompilerFlags.CORDEBUG_JIT_ENABLE_ENC: return false; default: Debug.Fail($"Unknown JIT compiler flags: {dnModule.CachedJITCompilerFlags}"); return null; } } static string GetFilename(string s) { try { return Path.GetFileName(s)!; } catch { } return s; } static void InitializeExeFields(DnModule dnModule, string filename, DbgImageLayout imageLayout, out bool isExe, out bool isDll, out DateTime? timestamp, out string version, out string? assemblySimpleName) { isExe = false; isDll = false; timestamp = null; version = string.Empty; assemblySimpleName = null; if (dnModule.IsDynamic) { if (dnModule.CorModule.IsManifestModule) version = new AssemblyNameInfo(dnModule.Assembly.FullName).Version.ToString(); } else if (dnModule.IsInMemory) { Debug.Assert(imageLayout == DbgImageLayout.File, nameof(GetFileVersion) + " assumes file layout"); var bytes = dnModule.Process.CorProcess.ReadMemory(dnModule.Address, (int)dnModule.Size); if (bytes is not null) { try { version = GetFileVersion(bytes); using (var peImage = new PEImage(bytes, imageLayout == DbgImageLayout.File ? ImageLayout.File : ImageLayout.Memory, true)) InitializeExeFieldsFrom(peImage, out isExe, out isDll, out timestamp, ref version, out assemblySimpleName); } catch { } } } else { try { version = GetFileVersion(filename); using (var peImage = new PEImage(filename)) InitializeExeFieldsFrom(peImage, out isExe, out isDll, out timestamp, ref version, out assemblySimpleName); } catch { } } } static void InitializeExeFieldsFrom(IPEImage peImage, out bool isExe, out bool isDll, out DateTime? timestamp, ref string version, out string? assemblySimpleName) { isExe = (peImage.ImageNTHeaders.FileHeader.Characteristics & Characteristics.Dll) == 0; isDll = !isExe; // Roslyn sets bit 31 if /deterministic is used (the low 31 bits is not a timestamp) if (peImage.ImageNTHeaders.FileHeader.TimeDateStamp < 0x80000000 && peImage.ImageNTHeaders.FileHeader.TimeDateStamp != 0) timestamp = Epoch.AddSeconds(peImage.ImageNTHeaders.FileHeader.TimeDateStamp); else timestamp = null; try { if (string.IsNullOrEmpty(version)) { using (var mod = ModuleDefMD.Load(peImage)) { if (string.IsNullOrEmpty(version)) version = mod.Assembly?.Version.ToString() ?? string.Empty; assemblySimpleName = UTF8String.ToSystemString(mod.Assembly?.Name); } } else { using (var md = MetadataFactory.CreateMetadata(peImage)) { if (!md.TablesStream.TryReadAssemblyRow(1, out var row)) assemblySimpleName = null; else assemblySimpleName = md.StringsStream.Read(row.Name); } } } catch { assemblySimpleName = null; } } static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); static string GetFileVersion(string filename) { if (!File.Exists(filename)) return string.Empty; try { var info = FileVersionInfo.GetVersionInfo(filename); return info.FileVersion ?? string.Empty; } catch { } return string.Empty; } static string GetFileVersion(byte[] bytes) { string? tempFilename = null; try { tempFilename = Path.GetTempFileName(); File.WriteAllBytes(tempFilename, bytes); return GetFileVersion(tempFilename); } catch { } finally { try { if (tempFilename is not null) File.Delete(tempFilename); } catch { } } return string.Empty; } } }