/* 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.Globalization; using System.IO; using dnlib.DotNet; using dnlib.DotNet.MD; using dnlib.PE; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet.Metadata.Internal; using dnSpy.Contracts.Debugger.Engine; using dnSpy.Debugger.DotNet.Metadata; using Mono.Debugger.Soft; namespace dnSpy.Debugger.DotNet.Mono.Impl { struct ModuleCreator { readonly DbgEngineImpl engine; readonly DbgObjectFactory objectFactory; readonly DbgAppDomain appDomain; readonly ModuleMirror monoModule; readonly int moduleOrder; ulong moduleAddress; uint moduleSize; bool isDynamic; bool isInMemory; ModuleCreator(DbgEngineImpl engine, DbgObjectFactory objectFactory, DbgAppDomain appDomain, ModuleMirror monoModule, int moduleOrder) { this.engine = engine; this.objectFactory = objectFactory; this.appDomain = appDomain; this.monoModule = monoModule; this.moduleOrder = moduleOrder; moduleAddress = 0; moduleSize = 0; isDynamic = false; isInMemory = false; } public static DbgEngineModule CreateModule(DbgEngineImpl engine, DbgObjectFactory objectFactory, DbgAppDomain appDomain, ModuleMirror monoModule, int moduleOrder, T data) where T : class => new ModuleCreator(engine, objectFactory, appDomain, monoModule, moduleOrder).CreateModuleCore(data); DbgEngineModule CreateModuleCore(T data) where T : class { DbgImageLayout imageLayout; string filename = monoModule.FullyQualifiedName; const string InMemoryModulePrefix = "data-"; if (filename.StartsWith(InMemoryModulePrefix, StringComparison.Ordinal)) { isDynamic = false; isInMemory = true; moduleAddress = 0; moduleSize = 0; imageLayout = DbgImageLayout.File; var hexAddrString = filename.Substring(InMemoryModulePrefix.Length); bool b = ulong.TryParse(hexAddrString, NumberStyles.HexNumber, null, out var inMemoryAddr); Debug.Assert(b); if (b) { moduleAddress = inMemoryAddr; b = PortableExecutableHelper.TryGetSizeOfImage(engine.DbgRuntime.Process, moduleAddress, imageLayout == DbgImageLayout.File, out uint imageSize); Debug.Assert(b); if (b) moduleSize = imageSize; } } else if (File.Exists(filename)) { filename = NormalizeFilename(filename); isDynamic = false; isInMemory = false; moduleAddress = 0; moduleSize = 0; if (PortableExecutableHelper.TryGetModuleAddressAndSize(engine.DbgRuntime.Process, filename, out var imageAddr, out var imageSize)) { moduleAddress = imageAddr; moduleSize = imageSize; } imageLayout = moduleAddress == 0 ? DbgImageLayout.File : DbgImageLayout.Memory; } else { isDynamic = true; isInMemory = true; moduleAddress = 0; moduleSize = 0; imageLayout = DbgImageLayout.Unknown; } string name = GetFilename(filename); bool? isOptimized = CalculateIsOptimized(); InitializeExeFields(filename, imageLayout, out var isExe, out var isDll, out var timestamp, out var version, out var assemblySimpleName); var reflectionAppDomain = ((DbgMonoDebugInternalAppDomainImpl)appDomain.InternalAppDomain).ReflectionAppDomain; var closedListenerCollection = new ClosedListenerCollection(); var getMetadata = CreateGetMetadataDelegate(closedListenerCollection, imageLayout); var fullyQualifiedName = DmdModule.GetFullyQualifiedName(isInMemory, isDynamic, filename); DmdAssembly reflectionAssembly; DmdModule reflectionModule; if (monoModule == monoModule.Assembly.ManifestModule) { var assemblyLocation = isInMemory || isDynamic ? string.Empty : filename; 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(monoModule.Assembly.ManifestModule); if (manifestModule is null) throw new InvalidOperationException(); reflectionAssembly = ((DbgMonoDebugInternalModuleImpl)manifestModule.InternalModule).ReflectionModule!.Assembly; reflectionModule = reflectionAppDomain.CreateModule(reflectionAssembly, getMetadata, isInMemory, isDynamic, fullyQualifiedName); } var internalModule = new DbgMonoDebugInternalModuleImpl(reflectionModule, closedListenerCollection); return objectFactory.CreateModule(appDomain, internalModule, isExe, moduleAddress, moduleSize, imageLayout, name, filename, isDynamic, isInMemory, isOptimized, moduleOrder, timestamp, version, engine.GetMessageFlags(), data: data, onCreated: engineModule => internalModule.SetModule(engineModule.Module)); } static string NormalizeFilename(string filename) { if (!File.Exists(filename)) return filename; try { return Path.GetFullPath(filename); } catch { } return filename; } Func CreateGetMetadataDelegate(ClosedListenerCollection closedListenerCollection, DbgImageLayout imageLayout) { if (isDynamic) return CreateDynamicGetMetadataDelegate(); else return CreateNormalGetMetadataDelegate(closedListenerCollection, imageLayout); } Func CreateDynamicGetMetadataDelegate() { Debug.Assert(isDynamic); return () => new DmdLazyMetadataBytesArray(Array.Empty(), false);//TODO: } Func CreateNormalGetMetadataDelegate(ClosedListenerCollection closedListenerCollection, DbgImageLayout imageLayout) { Debug.Assert(!isDynamic); var moduleAddressTmp = moduleAddress; var moduleSizeTmp = moduleSize; var engine = this.engine; var runtime = objectFactory.Runtime; var filename = monoModule.FullyQualifiedName; return () => { DbgRawMetadata? rawMd = null; try { if (moduleAddressTmp != 0 && moduleSizeTmp != 0) rawMd = engine.RawMetadataService.Create(runtime, imageLayout == DbgImageLayout.File, moduleAddressTmp, (int)moduleSizeTmp); else if (File.Exists(filename)) rawMd = engine.RawMetadataService.Create(runtime, true, File.ReadAllBytes(filename)); else { //TODO: rawMd = engine.RawMetadataService.Create(runtime, imageLayout == DbgImageLayout.File, Array.Empty()); } closedListenerCollection.Closed += (s, e) => rawMd.Release(); return new DmdLazyMetadataBytesPtr(rawMd.Address, (uint)rawMd.Size, rawMd.IsFileLayout); } catch { rawMd?.Release(); throw; } }; } bool? CalculateIsOptimized() { return null;//TODO: } static string GetFilename(string s) { try { return Path.GetFileName(s); } catch { } return s; } void InitializeExeFields(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 (isDynamic) { if (monoModule.Assembly.ManifestModule == monoModule) version = monoModule.Assembly.GetName().Version!.ToString(); } else if (isInMemory) { Debug.Assert(imageLayout == DbgImageLayout.File, nameof(GetFileVersion) + " assumes file layout"); var bytes = moduleSize == 0 ? null : engine.DbgRuntime.Process.ReadMemory(moduleAddress, (int)moduleSize); 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; } } }