From ea4673b51baaecf54a8b8409bdcc9d5b91490031 Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Wed, 15 Mar 2023 15:46:08 -0700 Subject: [PATCH] Add AssemblyLoadContext for loading extensions (#3649) * Add AssemblyLoadContext for loading extensions Add and use ServiceManager.NotifyExtensionLoadFailure event. Replace Provider scope with ProviderExport attribute Removing the DAC or DBI check in GetLocalPath() keeps invalid DACs or DBIs from being loaded if in the current directory. The download path will find a DAC in the same directory as the core dump when under dotnet-dump. Update extensibility doc * Update to 7.0.3 and 6.0.14 * Change the context service ordering to prevent creating runtimes if not needed * Fix analyzer issues * Fix overflow in WebApp tests * Code review feedback * Fix desktop SOS tests --- .../design-docs/dotnet-dump-extensibility.md | 10 +- eng/Versions.props | 7 +- .../AssemblyResolver.cs | 23 +- .../ContextService.cs | 22 +- .../ImageMappingMemoryService.cs | 2 +- ...ostics.DebugServices.Implementation.csproj | 1 + .../Runtime.cs | 4 - .../RuntimeProvider.cs | 2 +- .../ServiceManager.cs | 223 ++++++++++++++---- .../CommandServiceExtensions.cs | 5 + ...Microsoft.Diagnostics.DebugServices.csproj | 1 + .../ProviderExportAttribute.cs | 27 +++ .../ServiceExportAttribute.cs | 5 +- src/SOS/SOS.Extensions/HostServices.cs | 1 + src/Tools/dotnet-dump/Analyzer.cs | 1 + 15 files changed, 251 insertions(+), 83 deletions(-) rename src/{SOS/SOS.Extensions => Microsoft.Diagnostics.DebugServices.Implementation}/AssemblyResolver.cs (80%) create mode 100644 src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs diff --git a/documentation/design-docs/dotnet-dump-extensibility.md b/documentation/design-docs/dotnet-dump-extensibility.md index 3648bea98..8afd658bf 100644 --- a/documentation/design-docs/dotnet-dump-extensibility.md +++ b/documentation/design-docs/dotnet-dump-extensibility.md @@ -111,7 +111,7 @@ The threading model is single-threaded mainly because native debuggers like dbge The host is the debugger or program the command and the infrastructure runs on. The goal is to allow the same code for a command to run under different programs like the dotnet-dump REPL, lldb and Windows debuggers. Under Visual Studio the host will be a VS extension package. -When the host starts, the service manager loads command and service extension assemblies from the DOTNET_DIAGNOSTIC_EXTENSIONS environment variable (assembly paths separated by ';') or from the $HOME/.dotnet/extensions directory on Linux or MacOS or %USERPROFILE%\.dotnet\extensions directory on Windows. +When the host starts, the service manager loads command and service extension assemblies from the DOTNET_DIAGNOSTIC_EXTENSIONS environment variable (assembly paths separated by ';' on Windows or ":" on Linux/MacOS) or under the subdirectory "extensions" in the same directory as the infrastructure assemblies (Microsoft.Diagnostics.DebugServices.Implementation). #### IHost @@ -149,11 +149,11 @@ Services can be registered to contain common code for commands like [ClrMDHelper The [ServiceExport](../../src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs) attribute is used to mark classes, class constructors and factory methods to be registered as services. The ServiceScope defines where in the service hierarchy (global, context, target, runtime, thread, or module) this instance is available to commands and other services. -The [ServiceImport](../../src/Microsoft.Diagnostics.DebugServices/ServiceImportAttribute.cs) attribute is used to mark public or interal fields, properties and methods in commands and other services to receive a service instance. +The [ProviderExport](../../src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs) attribute is used to mark classes, class constructors and factory methods to be registered as "provider" which are extensions to a service. The IRuntimeService implementation uses this feature to enumerate all the IRuntimeProvider instances registered in the system. -The internal [ServiceManager](../../src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs) loads extension assemblies, provides the dependency injection using reflection (via the above attributes) and manages the various service factories. It creates the [IServiceContainer](../../src/Microsoft.Diagnostics.DebugServices/IServiceContainer.cs) instances for the extension points globally, in targets, modules, threads and runtimes (i.e. the IRuntime.ServiceProvider property). The public [IServiceManager](../../src/Microsoft.Diagnostics.DebugServices/IServiceManager.cs) interface exposes the public methods of the manager. +The [ServiceImport](../../src/Microsoft.Diagnostics.DebugServices/ServiceImportAttribute.cs) attribute is used to mark public or interal fields, properties and methods in commands and other services to receive a service instance. -The IServiceProvider/IServiceContainer implementation allows multiple instances of the same service type to be registered. They are queried by getting the IEnumerable of the service type (i.e. calling `IServiceProvider.GetService(typeof(IEnumerable)`). If the non-enumerable service type is queried and there are multiple instances, an exception is thrown. The IRuntimeService implementation uses this feature to enumerate all the IRuntimeProvider instances registered in the system. +The internal [ServiceManager](../../src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs) loads extension assemblies, provides the dependency injection using reflection (via the above attributes) and manages the various service factories. It creates the [ServiceContainerFactory](../../src/Microsoft.Diagnostics.DebugServices/ServiceContainerFactory.cs) instances for the extension points globally, in targets, modules, threads and runtimes (i.e. the IRuntime.ServiceProvider property). From the ServiceContainerFactory [ServiceContainer](../../src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs) instances are built. The public [IServiceManager](../../src/Microsoft.Diagnostics.DebugServices/IServiceManager.cs) interface exposes the public methods of the manager. ### IDumpTargetFactory @@ -239,7 +239,7 @@ This interface provides services to the native SOS/plugins code. It is a private This native interface is what the SOS.Extensions host uses to implement the above services. This is another private interface between SOS.Extensions and the native lldb plugin or Windows SOS native code. -[debuggerservice.h](../../src/SOS/inc/debuggerservice.h) for details. +[debuggerservice.h](../../src/SOS/inc/debuggerservices.h) for details. ## Projects and Assemblies diff --git a/eng/Versions.props b/eng/Versions.props index a557ae792..3375ffb6c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -28,13 +28,13 @@ - 6.0.12 + 6.0.14 $(MicrosoftNETCoreApp60Version) - 7.0.2 + 7.0.3 $(MicrosoftNETCoreApp70Version) $(MicrosoftNETCoreApp60Version) - $(MicrosoftNETCoreApp70Version) + 7.0.2 8.0.0-preview.2.23127.4 @@ -59,6 +59,7 @@ 2.0.0-beta1.20074.1 5.0.0 4.5.4 + 4.3.0 4.7.2 4.7.1 2.0.3 diff --git a/src/SOS/SOS.Extensions/AssemblyResolver.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs similarity index 80% rename from src/SOS/SOS.Extensions/AssemblyResolver.cs rename to src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs index 554ee2ee3..95eb3690d 100644 --- a/src/SOS/SOS.Extensions/AssemblyResolver.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs @@ -6,23 +6,24 @@ using System.Diagnostics; using System.IO; using System.Reflection; -namespace SOS.Extensions +namespace Microsoft.Diagnostics.DebugServices.Implementation { /// /// Used to enable app-local assembly unification. /// public static class AssemblyResolver { - private static bool s_initialized; + private static readonly string _defaultAssembliesPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + private static bool _initialized; /// /// Call to enable the assembly resolver for the current AppDomain. /// public static void Enable() { - if (!s_initialized) + if (!_initialized) { - s_initialized = true; + _initialized = true; AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; } } @@ -50,16 +51,12 @@ namespace SOS.Extensions } // Look next to the executing assembly - assemblyPath = Assembly.GetExecutingAssembly().Location; - if (!string.IsNullOrEmpty(assemblyPath)) + probingPath = Path.Combine(_defaultAssembliesPath, fileName); + Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly"); + if (Probe(probingPath, referenceName.Version, out assembly)) { - probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName); - Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly"); - if (Probe(probingPath, referenceName.Version, out assembly)) - { - Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly"); - return assembly; - } + Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly"); + return assembly; } return null; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs index 9857aea68..e4df92613 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs @@ -266,6 +266,17 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { return _contextService.GetCurrentTarget(); } + // Check the current target (if exists) for the service. + ITarget currentTarget = _contextService.GetCurrentTarget(); + if (currentTarget is not null) + { + // This will chain to the global services if not found in the current target + object service = currentTarget.Services.GetService(type); + if (service is not null) + { + return service; + } + } // Check the current runtime (if exists) for the service. IRuntime currentRuntime = _contextService.GetCurrentRuntime(); if (currentRuntime is not null) @@ -288,17 +299,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation return service; } } - // Check the current target (if exists) for the service. - ITarget currentTarget = _contextService.GetCurrentTarget(); - if (currentTarget is not null) - { - // This will chain to the global services if not found in the current target - object service = currentTarget.Services.GetService(type); - if (service is not null) - { - return service; - } - } // Check with the global host services. return _contextService._host.Services.GetService(type); } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs index 246343e3a..35fb9c9af 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs @@ -300,7 +300,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation if ((offset + sizeof(ulong)) <= data.Length) { ulong value = BitConverter.ToUInt64(data, offset); - value += baseDelta; + unchecked { value += baseDelta; } byte[] source = BitConverter.GetBytes(value); Array.Copy(source, 0, data, offset, source.Length); } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj index d36528d89..06c99c30c 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs index 8b9e352e4..40b237f88 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs @@ -177,10 +177,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation private string GetLocalPath(string fileName) { - if (File.Exists(fileName)) - { - return fileName; - } string localFilePath; if (!string.IsNullOrEmpty(RuntimeModuleDirectory)) { diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs index 24b0222f8..a755a13d6 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// /// ClrMD runtime provider implementation /// - [ServiceExport(Type = typeof(IRuntimeProvider), Scope = ServiceScope.Provider)] + [ProviderExport(Type = typeof(IRuntimeProvider))] public class RuntimeProvider : IRuntimeProvider { private readonly IServiceProvider _services; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs index 714f3076d..090270e28 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; namespace Microsoft.Diagnostics.DebugServices.Implementation { @@ -18,13 +20,29 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { private readonly Dictionary[] _factories; private readonly Dictionary> _providerFactories; - private readonly ServiceEvent _notifyExtensionLoad; + private readonly List _extensions; private bool _finalized; /// /// This event fires when an extension assembly is loaded /// - public IServiceEvent NotifyExtensionLoad => _notifyExtensionLoad; + public IServiceEvent NotifyExtensionLoad { get; } + + /// + /// This event fires when an extension assembly fails + /// + public IServiceEvent NotifyExtensionLoadFailure { get; } + + /// + /// Enable the assembly resolver on desktop Framework + /// + static ServiceManager() + { + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) + { + AssemblyResolver.Enable(); + } + } /// /// Create a service manager instance @@ -33,7 +51,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { _factories = new Dictionary[(int)ServiceScope.Max]; _providerFactories = new Dictionary>(); - _notifyExtensionLoad = new ServiceEvent(); + _extensions = new List(); + NotifyExtensionLoad = new ServiceEvent(); + NotifyExtensionLoadFailure = new ServiceEvent(); for (int i = 0; i < (int)ServiceScope.Max; i++) { _factories[i] = new Dictionary(); @@ -44,7 +64,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// Creates a new service container factory with all the registered factories for the given scope. /// /// global, per-target, per-runtime, etc. service type - /// parent service provider to chain + /// parent service services to chain /// public ServiceContainerFactory CreateServiceContainerFactory(ServiceScope scope, IServiceProvider parent) { @@ -52,22 +72,20 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { throw new InvalidOperationException(); } - return new ServiceContainerFactory(parent, _factories[(int)scope]); } /// - /// Get the provider factories for a type or interface. + /// Get the services factories for a type or interface. /// /// type or interface - /// the provider factories for the type + /// the services factories for the type public IEnumerable EnumerateProviderFactories(Type providerType) { if (!_finalized) { throw new InvalidOperationException(); } - if (_providerFactories.TryGetValue(providerType, out List factories)) { return factories; @@ -79,6 +97,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// Finds all the ServiceExport attributes in the assembly and registers. /// /// service implementation assembly + /// assembly or reference not found + /// not supported public void RegisterExportedServices(Assembly assembly) { foreach (Type serviceType in assembly.GetExportedTypes()) @@ -100,7 +120,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { throw new InvalidOperationException(); } - for (Type currentType = serviceType; currentType is not null; currentType = currentType.BaseType) { if (currentType == typeof(object) || currentType == typeof(ValueType)) @@ -110,23 +129,63 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation ServiceExportAttribute serviceAttribute = currentType.GetCustomAttribute(inherit: false); if (serviceAttribute is not null) { - ServiceFactory factory = (provider) => Utilities.CreateInstance(serviceType, provider); + ServiceFactory factory = (services) => Utilities.CreateInstance(serviceType, services); AddServiceFactory(serviceAttribute.Scope, serviceAttribute.Type ?? serviceType, factory); } + ProviderExportAttribute providerAttribute = currentType.GetCustomAttribute(inherit: false); + if (providerAttribute is not null) + { + ServiceFactory factory = (services) => Utilities.CreateInstance(serviceType, services); + AddProviderFactory(providerAttribute.Type ?? serviceType, factory); + } // The method or constructor must be static and public foreach (MethodInfo methodInfo in currentType.GetMethods(BindingFlags.Static | BindingFlags.Public)) { serviceAttribute = methodInfo.GetCustomAttribute(inherit: false); if (serviceAttribute is not null) { - AddServiceFactory(serviceAttribute.Scope, serviceAttribute.Type ?? methodInfo.ReturnType, (provider) => Utilities.CreateInstance(methodInfo, provider)); + ServiceFactory factory = (services) => Utilities.CreateInstance(methodInfo, services); + AddServiceFactory(serviceAttribute.Scope, serviceAttribute.Type ?? methodInfo.ReturnType, factory); + } + providerAttribute = currentType.GetCustomAttribute(inherit: false); + if (providerAttribute is not null) + { + ServiceFactory factory = (services) => Utilities.CreateInstance(methodInfo, services); + AddProviderFactory(providerAttribute.Type ?? methodInfo.ReturnType, factory); } } } } /// - /// Add service containerFactory for the specific scope. + /// Register the exported services in the assembly and notify the assembly has loaded. + /// + /// extension assembly + public void RegisterAssembly(Assembly assembly) + { + if (_finalized) + { + throw new InvalidOperationException(); + } + try + { + RegisterExportedServices(assembly); + NotifyExtensionLoad.Fire(assembly); + } + catch (Exception ex) when + (ex is DiagnosticsException + or ArgumentException + or NotSupportedException + or FileLoadException + or FileNotFoundException) + { + Trace.TraceError(ex.ToString()); + NotifyExtensionLoadFailure.Fire(new DiagnosticsException($"Extension load failure - {ex.Message} {assembly.Location}", ex)); + } + } + + /// + /// Add service factory for the specific scope. /// /// service type /// global, per-target, per-runtime, etc. service type @@ -134,7 +193,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public void AddServiceFactory(ServiceScope scope, ServiceFactory factory) => AddServiceFactory(scope, typeof(T), factory); /// - /// Add service containerFactory for the specific scope. + /// Add service factory for the specific scope. /// /// global, per-target, per-runtime, etc. service type /// service type or interface @@ -145,26 +204,40 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { throw new ArgumentNullException(nameof(factory)); } - if (_finalized) { throw new InvalidOperationException(); } + _factories[(int)scope].Add(serviceType, factory); + } - if (scope == ServiceScope.Provider) + /// + /// Add provider factory. + /// + /// service type or interface + /// function to create provider instance + public void AddProviderFactory(Type providerType, ServiceFactory factory) + { + if (factory is null) { - if (!_providerFactories.TryGetValue(serviceType, out List factories)) - { - _providerFactories.Add(serviceType, factories = new List()); - } - factories.Add(factory); + throw new ArgumentNullException(nameof(factory)); } - else + if (_finalized) { - _factories[(int)scope].Add(serviceType, factory); + throw new InvalidOperationException(); + } + if (!_providerFactories.TryGetValue(providerType, out List factories)) + { + _providerFactories.Add(providerType, factories = new List()); } + factories.Add(factory); } + /// + /// Finalizes the service manager. Loading extensions or adding service factories are not allowed after this call. + /// + public void FinalizeServices() => _finalized = true; + /// /// Load any extra extensions in the search path /// @@ -174,7 +247,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { throw new InvalidOperationException(); } - List extensionPaths = new(); string diagnosticExtensions = Environment.GetEnvironmentVariable("DOTNET_DIAGNOSTIC_EXTENSIONS"); if (!string.IsNullOrEmpty(diagnosticExtensions)) @@ -193,7 +265,12 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation string[] extensionFiles = Directory.GetFiles(searchPath, "*.dll"); extensionPaths.AddRange(extensionFiles); } - catch (Exception ex) when (ex is IOException or ArgumentException or UnauthorizedAccessException or System.Security.SecurityException) + catch (Exception ex) when + (ex is IOException + or ArgumentException + or BadImageFormatException + or UnauthorizedAccessException + or System.Security.SecurityException) { Trace.TraceError(ex.ToString()); } @@ -215,15 +292,28 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { throw new InvalidOperationException(); } - Assembly assembly = null; try { - assembly = Assembly.LoadFrom(extensionPath); + // Assembly load contexts are not supported by the desktop framework + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) + { + assembly = Assembly.LoadFile(extensionPath); + } + else + { + assembly = UseAssemblyLoadContext(extensionPath); + } } - catch (Exception ex) when (ex is IOException or ArgumentException or BadImageFormatException or System.Security.SecurityException) + catch (Exception ex) when + (ex is IOException + or ArgumentException + or InvalidOperationException + or BadImageFormatException + or System.Security.SecurityException) { Trace.TraceError(ex.ToString()); + NotifyExtensionLoadFailure.Fire(ex); } if (assembly is not null) { @@ -232,30 +322,79 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation } /// - /// Register the exported services in the assembly and notify the assembly has loaded. + /// Load the extension using an assembly load context. This needs to be in + /// a separate method so ExtensionLoadContext class doesn't get referenced + /// when running on desktop Framework. /// - /// extension assembly - public void RegisterAssembly(Assembly assembly) + /// extension assembly path + /// assembly + private Assembly UseAssemblyLoadContext(string extensionPath) { - if (_finalized) + ExtensionLoadContext extension = new(extensionPath); + Assembly assembly = extension.LoadFromAssemblyPath(extensionPath); + if (assembly is not null) { - throw new InvalidOperationException(); + // This list is just to keep the load context alive + _extensions.Add(extension); } + return assembly; + } - try + private sealed class ExtensionLoadContext : AssemblyLoadContext + { + private static readonly HashSet s_defaultAssemblies = new() { + "Microsoft.Diagnostics.DebugServices", + "Microsoft.Diagnostics.DebugServices.Implementation", + "Microsoft.Diagnostics.ExtensionCommands", + "Microsoft.Diagnostics.NETCore.Client", + "Microsoft.Diagnostics.Repl", + "Microsoft.Diagnostics.Runtime", + "Microsoft.FileFormats", + "Microsoft.SymbolStore", + "SOS.Extensions", + "SOS.Hosting", + "SOS.InstallHelper" + }; + + private static readonly string _defaultAssembliesPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + private readonly string _extensionPath; + private Dictionary _extensionPaths; + + public ExtensionLoadContext(string extensionPath) { - RegisterExportedServices(assembly); - _notifyExtensionLoad.Fire(assembly); + _extensionPath = extensionPath; } - catch (Exception ex) when (ex is DiagnosticsException or NotSupportedException or FileNotFoundException) + + protected override Assembly Load(AssemblyName assemblyName) { - Trace.TraceError(ex.ToString()); + lock (this) + { + if (_extensionPaths == null) + { + string[] extensionFiles = Directory.GetFiles(Path.GetDirectoryName(_extensionPath), "*.dll"); + _extensionPaths = new Dictionary(); + foreach (string file in extensionFiles) + { + _extensionPaths.Add(Path.GetFileNameWithoutExtension(file), file); + } + } + } + if (s_defaultAssemblies.Contains(assemblyName.Name)) + { + Assembly assembly = Default.LoadFromAssemblyPath(Path.Combine(_defaultAssembliesPath, assemblyName.Name) + ".dll"); + if (assemblyName.Version.Major != assembly.GetName().Version.Major) + { + throw new InvalidOperationException($"Extension assembly reference version not supported for {assemblyName.Name} {assemblyName.Version}"); + } + return assembly; + } + else if (_extensionPaths.TryGetValue(assemblyName.Name, out string path)) + { + return LoadFromAssemblyPath(path); + } + return null; } } - - /// - /// Finalizes the service manager. Loading extensions or adding service factories are not allowed after this call. - /// - public void FinalizeServices() => _finalized = true; } } diff --git a/src/Microsoft.Diagnostics.DebugServices/CommandServiceExtensions.cs b/src/Microsoft.Diagnostics.DebugServices/CommandServiceExtensions.cs index 21a1318cc..b88f43a3f 100644 --- a/src/Microsoft.Diagnostics.DebugServices/CommandServiceExtensions.cs +++ b/src/Microsoft.Diagnostics.DebugServices/CommandServiceExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; @@ -15,6 +16,8 @@ namespace Microsoft.Diagnostics.DebugServices /// /// command service instance /// list of assemblies to search + /// assembly or reference not found + /// not supported public static void AddCommands(this ICommandService commandService, IEnumerable assemblies) { commandService.AddCommands(assemblies.SelectMany((assembly) => assembly.GetExportedTypes())); @@ -25,6 +28,8 @@ namespace Microsoft.Diagnostics.DebugServices /// /// command service instance /// assembly to search for commands + /// assembly or reference not found + /// not supported public static void AddCommands(this ICommandService commandService, Assembly assembly) { commandService.AddCommands(assembly.GetExportedTypes()); diff --git a/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj b/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj index a441d0e6b..33f0fe3e9 100644 --- a/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj +++ b/src/Microsoft.Diagnostics.DebugServices/Microsoft.Diagnostics.DebugServices.csproj @@ -4,6 +4,7 @@ true ;1591;1701 Diagnostics debug services + 7.0.0 true Diagnostic $(Description) diff --git a/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs b/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs new file mode 100644 index 000000000..8a70aff35 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/ProviderExportAttribute.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DebugServices +{ + /// + /// Marks classes or methods (provider factories) as providers (extensions to services). + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class ProviderExportAttribute : Attribute + { + /// + /// The interface or type to register the provider. If null, the provider type registered will be + /// he class itself or the return type of the method. + /// + public Type Type { get; set; } + + /// + /// Default constructor. + /// + public ProviderExportAttribute() + { + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs index 1e22738f6..2258e8cac 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs @@ -9,7 +9,6 @@ namespace Microsoft.Diagnostics.DebugServices { Global, Context, - Provider, Target, Module, Thread, @@ -24,8 +23,8 @@ namespace Microsoft.Diagnostics.DebugServices public class ServiceExportAttribute : Attribute { /// - /// The interface or type to register the service. If null, the service type registered will be any - /// interfaces on the the class, the class itself if no interfaces or the return type of the method. + /// The interface or type to register the service. If null, the service type registered will be + /// the class itself or the return type of the method. /// public Type Type { get; set; } diff --git a/src/SOS/SOS.Extensions/HostServices.cs b/src/SOS/SOS.Extensions/HostServices.cs index 0799b08a0..900f6f43c 100644 --- a/src/SOS/SOS.Extensions/HostServices.cs +++ b/src/SOS/SOS.Extensions/HostServices.cs @@ -208,6 +208,7 @@ namespace SOS.Extensions // Display any extension assembly loads on console _serviceManager.NotifyExtensionLoad.Register((Assembly assembly) => fileLoggingConsoleService.WriteLine($"Loading extension {assembly.Location}")); + _serviceManager.NotifyExtensionLoadFailure.Register((Exception ex) => fileLoggingConsoleService.WriteLine(ex.Message)); // Load any extra extensions in the search path _serviceManager.LoadExtensions(); diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs index 2a0c765e9..6718677d6 100644 --- a/src/Tools/dotnet-dump/Analyzer.cs +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -91,6 +91,7 @@ namespace Microsoft.Diagnostics.Tools.Dump // Display any extension assembly loads on console _serviceManager.NotifyExtensionLoad.Register((Assembly assembly) => _fileLoggingConsoleService.WriteLine($"Loading extension {assembly.Location}")); + _serviceManager.NotifyExtensionLoadFailure.Register((Exception ex) => _fileLoggingConsoleService.WriteLine(ex.Message)); // Load any extra extensions _serviceManager.LoadExtensions(); -- 2.34.1