/* 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 dndbg.DotNet; using dnlib.DotNet; using dnlib.DotNet.MD; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet.Metadata; using dnSpy.Contracts.Metadata; using dnSpy.Debugger.DotNet.CorDebug.Impl; namespace dnSpy.Debugger.DotNet.CorDebug.Metadata { [Export(typeof(DbgDynamicModuleProviderFactory))] sealed class DbgDynamicModuleProviderFactoryImpl : DbgDynamicModuleProviderFactory { public override DbgDynamicModuleProvider? Create(DbgRuntime runtime) { var engine = DbgEngineImpl.TryGetEngine(runtime); if (engine is not null) return runtime.GetOrCreateData(() => new DbgDynamicModuleProviderImpl(engine)); return null; } } sealed class DbgDynamicModuleProviderImpl : DbgDynamicModuleProvider { public override event EventHandler? ClassLoaded; readonly DbgEngineImpl engine; public DbgDynamicModuleProviderImpl(DbgEngineImpl engine) { this.engine = engine ?? throw new ArgumentNullException(nameof(engine)); engine.ClassLoaded += DbgEngineImpl_ClassLoaded; } void DbgEngineImpl_ClassLoaded(object? sender, ClassLoadedEventArgs e) => ClassLoaded?.Invoke(this, e); public override void BeginInvoke(Action callback) => engine.CorDebugThread(callback); T Invoke(Func callback) => engine.InvokeCorDebugThread(callback); sealed class DynamicModuleData { public LastValidRids LastValidRids; public CorModuleDef? Metadata; public ModuleId ModuleId; } public override ModuleDef? GetDynamicMetadata(DbgModule module, out ModuleId moduleId) { var data = module.GetOrCreateData(); if (data.Metadata is not null) { moduleId = data.ModuleId; return data.Metadata; } var info = Invoke(() => { if (data.Metadata is not null) return (metadata: data.Metadata, moduleId: data.ModuleId); var info2 = engine.GetDynamicMetadata_EngineThread(module); if (info2.metadata is not null) { // DsDotNetDocumentBase sets EnableTypeDefFindCache to true and that property accesses the // Types property. It must be initialized in the correct thread. _ = info2.metadata.Types; info2.metadata.DisableMDAPICalls = true; } return info2; }); data.ModuleId = info.moduleId; data.Metadata = info.metadata; moduleId = info.moduleId; return data.Metadata; } CorModuleDef? TryGetDynamicMetadata(DbgModule module) => module.GetOrCreateData().Metadata; public override void LoadEverything(DbgModule[] modules, bool started) { engine.VerifyCorDebugThread(); foreach (var module in modules) { var md = TryGetDynamicMetadata(module); if (md is not null) md.DisableMDAPICalls = !started; } } public override IEnumerable GetModifiedTypes(DbgModule module) { engine.VerifyCorDebugThread(); var data = module.GetOrCreateData(); var cmod = TryGetDynamicMetadata(module); var hash = new HashSet(); if (cmod is null) return hash; var oldLastValid = UpdateLastValidRids(data, cmod); var lastValid = data.LastValidRids; if (oldLastValid.Equals(lastValid)) return hash; const uint TYPEDEF_TOKEN = 0x02000000; // Optimization if we loaded a big file if (oldLastValid.TypeDefRid == 0) { for (uint rid = 1; rid <= lastValid.TypeDefRid; rid++) hash.Add(TYPEDEF_TOKEN + rid); return hash; } var methodRids = new HashSet(); var gpRids = new HashSet(); for (uint rid = oldLastValid.TypeDefRid + 1; rid <= lastValid.TypeDefRid; rid++) hash.Add(TYPEDEF_TOKEN + rid); for (uint rid = oldLastValid.FieldRid + 1; rid <= lastValid.FieldRid; rid++) { var typeOwner = cmod.GetFieldOwnerToken(rid); if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } for (uint rid = oldLastValid.MethodRid + 1; rid <= lastValid.MethodRid; rid++) { methodRids.Add(rid); var typeOwner = cmod.GetMethodOwnerToken(rid); if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } for (uint rid = oldLastValid.ParamRid + 1; rid <= lastValid.ParamRid; rid++) { var methodOwner = cmod.GetParamOwnerToken(rid); if (methodRids.Contains(methodOwner.Rid)) continue; var typeOwner = cmod.GetMethodOwnerToken(methodOwner.Rid); if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } for (uint rid = oldLastValid.EventRid + 1; rid <= lastValid.EventRid; rid++) { var typeOwner = cmod.GetEventOwnerToken(rid); if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } for (uint rid = oldLastValid.PropertyRid + 1; rid <= lastValid.PropertyRid; rid++) { var typeOwner = cmod.GetPropertyOwnerToken(rid); if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } for (uint rid = oldLastValid.GenericParamRid + 1; rid <= lastValid.GenericParamRid; rid++) { gpRids.Add(rid); var ownerToken = cmod.GetGenericParamOwnerToken(rid); MDToken typeOwner; if (ownerToken.Table == Table.TypeDef) typeOwner = ownerToken; else if (ownerToken.Table == Table.Method) { if (methodRids.Contains(ownerToken.Rid)) continue; typeOwner = cmod.GetMethodOwnerToken(ownerToken.Rid); } else continue; if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } for (uint rid = oldLastValid.GenericParamConstraintRid + 1; rid <= lastValid.GenericParamConstraintRid; rid++) { var gpOwner = cmod.GetGenericParamConstraintOwnerToken(rid); if (gpRids.Contains(gpOwner.Rid)) continue; var ownerToken = cmod.GetGenericParamOwnerToken(gpOwner.Rid); MDToken typeOwner; if (ownerToken.Table == Table.TypeDef) typeOwner = ownerToken; else if (ownerToken.Table == Table.Method) { if (methodRids.Contains(ownerToken.Rid)) continue; typeOwner = cmod.GetMethodOwnerToken(ownerToken.Rid); } else continue; if (typeOwner.Rid != 0) hash.Add(typeOwner.Raw); } return hash; } public override void InitializeNonLoadedClasses(DbgModule module, uint[] nonLoadedTokens) { engine.VerifyCorDebugThread(); var cmod = TryGetDynamicMetadata(module); Debug2.Assert(cmod is not null); if (cmod is null) return; foreach (uint token in nonLoadedTokens) cmod.ForceInitializeTypeDef(token & 0x00FFFFFF); } LastValidRids UpdateLastValidRids(DynamicModuleData data, CorModuleDef module) { var old = data.LastValidRids; // Linear search but shouldn't be a problem except the first time if we load a big file for (; ; data.LastValidRids.TypeDefRid++) { if (!module.IsValidToken(new MDToken(Table.TypeDef, data.LastValidRids.TypeDefRid + 1).Raw)) break; } for (; ; data.LastValidRids.FieldRid++) { if (!module.IsValidToken(new MDToken(Table.Field, data.LastValidRids.FieldRid + 1).Raw)) break; } for (; ; data.LastValidRids.MethodRid++) { if (!module.IsValidToken(new MDToken(Table.Method, data.LastValidRids.MethodRid + 1).Raw)) break; } for (; ; data.LastValidRids.ParamRid++) { if (!module.IsValidToken(new MDToken(Table.Param, data.LastValidRids.ParamRid + 1).Raw)) break; } for (; ; data.LastValidRids.EventRid++) { if (!module.IsValidToken(new MDToken(Table.Event, data.LastValidRids.EventRid + 1).Raw)) break; } for (; ; data.LastValidRids.PropertyRid++) { if (!module.IsValidToken(new MDToken(Table.Property, data.LastValidRids.PropertyRid + 1).Raw)) break; } for (; ; data.LastValidRids.GenericParamRid++) { if (!module.IsValidToken(new MDToken(Table.GenericParam, data.LastValidRids.GenericParamRid + 1).Raw)) break; } for (; ; data.LastValidRids.GenericParamConstraintRid++) { if (!module.IsValidToken(new MDToken(Table.GenericParamConstraint, data.LastValidRids.GenericParamConstraintRid + 1).Raw)) break; } return old; } } }