Add service manager and reflection based service discovery (#3448)
authorMike McLaughlin <mikem@microsoft.com>
Sat, 11 Feb 2023 02:19:30 +0000 (18:19 -0800)
committerGitHub <noreply@github.com>
Sat, 11 Feb 2023 02:19:30 +0000 (18:19 -0800)
* Add service manager and reflection based service discovery

Add GetTargetId to native ITarget

Load service and command assemblies from directory.

Add LoadExtensions

Add IRuntimeProvider interface that allows IRuntime implementations to be added to the IRuntimeService.

Support multiple service instances for IRuntimeProvider. Added IServiceProvider.GetServices() extension method to get them.

Added the ContextService.ContextServiceProvider to deal with the context service's special needs.

Added the ServiceImportAttribute support. There are Utilities helper functions that process this attribute.

Add IHost.OnTargetCreate event. Removed IHost.DestroyTarget and add ITarget.Destroy. Allows the context and other services
to managed state when targets come, go and flush. Makes target destruction more explicit by the implementations.

Use the ServiceImportAttribute in commands. Add "Optional" ServiceImportAttribute flag.

Add IServiceProviderManager interface to abstract the ServiceProvider functions.

Add as a service the PEModule wrapper around PEReader, ElfModule around ElfFile and MachOModule around MachOFile.

Pass IServiceProvider to constructors instead of using ITarget.Services. This removes the recursion from the ImageMappingMemoryService and MetdataMappingMemoryService.

Update extensibility doc

Add starting runtime id to runtime providers

Add native type/field interfaces/impls

Add WriteLine() to CommandBase and console service extensions.

Fix module Version property.

Fix testasset xml generation and repackaging scripts

Update symstore version

Update test assets version

* Add switch by id to `runtimes` command

* Catch load exceptions

* Change to loading extensions from the tool/host install directory

* Code review feedback

* Undo some of the code review feedback

* Code review feedback

Removed weird this parameter from IServiceContainer.DisposeServices(). Use RemoveServices() to prevent recursion.

Removed IServiceContainer.GetCachedService extension method and call IServiceContainer.TryGetCachedService directly.

* More Code review feedback

* More code review feedback

* More code review changes

* Add provider registration support to service manager

Instead of supporting multiple instances of the same service type, this change allows
services to get "provider" factories registered up the Provider scope.  This simplifies
the service container implementation.

Add EnumerateProviderFactories to IServiceManager.

* Allow ServiceImportAttribute on method and constructor parameters to control the Optional metadata flag.

* Code review feedback

* CR feedback: Better PEModule, ELFModule and MachOModule service implementation

The service container no longer exposes itself to services.

* CR feedback: cleanup service manager's global/context container support

* Refactor IServiceContainer to ServiceContainerFactory and ServiceContainer

Remove interface and put implementations in DebugServices.

* Code review feedback

* Code review

124 files changed:
documentation/design-docs/dotnet-dump-extensibility.md
eng/testassets/rebuild/TestAssets.Linux.arm64.3.1.nuspec
eng/testassets/rebuild/TestAssets.Linux.arm64.5.0.nuspec
eng/testassets/rebuild/TestAssets.Linux.arm64.6.0.nuspec
eng/testassets/rebuild/TestAssets.Linux.x64.3.1.nuspec
eng/testassets/rebuild/TestAssets.Linux.x64.5.0.nuspec
eng/testassets/rebuild/TestAssets.Linux.x64.6.0.nuspec
eng/testassets/rebuild/TestAssets.Windows.x64.3.1.nuspec
eng/testassets/rebuild/TestAssets.Windows.x64.5.0.nuspec
eng/testassets/rebuild/TestAssets.Windows.x64.6.0.nuspec
eng/testassets/rebuild/TestAssets.Windows.x86.3.1.nuspec
eng/testassets/rebuild/TestAssets.Windows.x86.5.0.nuspec
eng/testassets/rebuild/TestAssets.Windows.x86.6.0.nuspec
eng/testassets/rebuild/rewritexml.cmd
eng/testassets/writexml.cmd
eng/testassets/writexml.sh
eng/testassets/writexml_x86.cmd
src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ElfModule.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/MachOModule.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ManagedImageMappingModuleService.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/MetadataMappingMemoryService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleServiceFromDataReader.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/PEModule.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs [deleted file]
src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs
src/Microsoft.Diagnostics.DebugServices/CommandBase.cs
src/Microsoft.Diagnostics.DebugServices/CommandServiceExtensions.cs
src/Microsoft.Diagnostics.DebugServices/ConsoleServiceExtensions.cs
src/Microsoft.Diagnostics.DebugServices/ContextServiceExtensions.cs
src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs
src/Microsoft.Diagnostics.DebugServices/ICommandService.cs
src/Microsoft.Diagnostics.DebugServices/IContextService.cs
src/Microsoft.Diagnostics.DebugServices/IField.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/IHost.cs
src/Microsoft.Diagnostics.DebugServices/IModuleSymbols.cs
src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
src/Microsoft.Diagnostics.DebugServices/IRuntimeProvider.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/IServiceEvent.cs
src/Microsoft.Diagnostics.DebugServices/IServiceManager.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ITarget.cs
src/Microsoft.Diagnostics.DebugServices/IType.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ServiceContainerFactory.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ServiceImportAttribute.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ServiceManagerExtensions.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ServiceProviderExtensions.cs
src/Microsoft.Diagnostics.DebugServices/TargetExtensions.cs
src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs
src/Microsoft.Diagnostics.ExtensionCommands/ClrModulesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ConsoleLoggingCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/RegistersCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/SetClrPathCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/StatusCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ThreadsCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs
src/Microsoft.Diagnostics.Repl/HelpCommand.cs [deleted file]
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs
src/Microsoft.Diagnostics.TestHelpers/TestHost/TestHost.cs
src/SOS/SOS.Extensions/ContextServiceFromDebuggerServices.cs
src/SOS/SOS.Extensions/DebuggerServices.cs
src/SOS/SOS.Extensions/HostServices.cs
src/SOS/SOS.Extensions/MemoryServiceFromDebuggerServices.cs
src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs
src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs
src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs
src/SOS/SOS.Hosting/Commands/SOSCommand.cs
src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs
src/SOS/SOS.Hosting/DataTargetWrapper.cs
src/SOS/SOS.Hosting/HostWrapper.cs
src/SOS/SOS.Hosting/LLDBServices.cs
src/SOS/SOS.Hosting/RuntimeWrapper.cs
src/SOS/SOS.Hosting/SOSHost.cs
src/SOS/SOS.Hosting/SOSLibrary.cs
src/SOS/SOS.Hosting/ServiceWrapper.cs
src/SOS/SOS.Hosting/SymbolServiceWrapper.cs
src/SOS/SOS.Hosting/TargetWrapper.cs
src/SOS/SOS.UnitTests/Scripts/DualRuntimes.script
src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script
src/SOS/Strike/dbgengservices.cpp
src/SOS/Strike/dbgengservices.h
src/SOS/Strike/symbols.cpp
src/SOS/extensions/extensions.cpp
src/SOS/inc/debuggerservices.h
src/SOS/lldbplugin/services.cpp
src/SOS/lldbplugin/services.h
src/Tools/dotnet-dump/Analyzer.cs
src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs
src/Tools/dotnet-dump/Commands/SOSCommand.cs
src/tests/DbgShim.UnitTests/DbgShim.UnitTests.csproj
src/tests/DbgShim.UnitTests/DbgShimTests.cs
src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/Microsoft.Diagnostics.DebugServices.UnitTests.csproj
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/RunTests.cs
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs

index 64bf62224905588596501f1a5b5e2148e6f7e808..3648bea9889644be84ae69870e89b10edf4f75eb 100644 (file)
@@ -2,7 +2,7 @@
 
 This document describes a mechanism to allow first and third party users to add custom commands and services to `dotnet-dump` and `SOS` on the supported debuggers. Such extensibility has been a frequent ask from companies like Criteo and some teams at Microsoft. The goal is to write the code for a command once and have it run under all the supported debuggers, including dotnet-dump.
 
-Internally, the ability to host commands like the future `gcheapdiff` under dotnet-dump, lldb and cdb/windbg will be invaluable for the productivity of developers in the ecosystem. Implementing new commands and features in C# is far easier and more productive for the interested parties. Other people on .NET team and in the community are more likely to contribute improvements to our tools, similar to what Stephen did with `dumpasync`. Unlike the plugin situation, if they contribute directly to our repo then the improvements will automatically flow to all customers and provide broader value.
+The ability to host commands like the future `gcheapdiff` under dotnet-dump, lldb, cdb/windbg and Visual Studio will be invaluable for the productivity of developers in the ecosystem. Implementing new commands and features in C# is far easier and more productive for the interested parties. Other people on .NET team and in the community are more likely to contribute improvements to our tools, similar to what Stephen did with `dumpasync`. Unlike the plugin situation, if they contribute directly to our repo then the improvements will automatically flow to all customers and provide broader value.
 
 This effort is part of the "unified extensiblity" models - where various teams are coming together to define a common abstraction across all debuggers and debugger like hosts (dotnet-dump). Services such as Azure Watson could use this infrastructure to write a commands akin to `!analyze` and other analysis tools using a subset of the DAC - provided as a service - to do some unhandled exception and stack trace triage.
 
@@ -17,7 +17,7 @@ This effort is part of the "unified extensiblity" models - where various teams a
    - Visual Studio
  - Create various "target" types from the command line or a command from:
    - Windows minidumps
-   - Linux coredumps
+   - Linux and MacOS coredumps
    - Live process snapshots 
  
 ## Customer Value
@@ -26,7 +26,7 @@ This effort is part of the "unified extensiblity" models - where various teams a
     - Commands that CSS devs would find useful in Visual Studio that can't be done in VS any other way:
         - !GCHandles - Provides statistics about GCHandles in the process.
         - !ThreadPool - This command lists basic information about the ThreadPool, including the number of work requests in the queue, number of completion port threads, and number of timers.
-        - !SaveModule - This command allows you to take a image loaded in memory and write it to a file. This is especially useful if you are debugging a full memory dump, and saves a PE module to a file from a dump, and don't have the original DLLs or EXEs.
+        - !SaveModule or !SaveAllModules ([#3138](https://github.com/dotnet/diagnostics/issues/3138)) - This command allows you to take a image loaded in memory and write it to a file. This is especially useful if you are debugging a full memory dump, and saves a PE module to a file from a dump, and don't have the original DLLs or EXEs.
         - rest of list TBD.
         
 - Enables support for Azure Geneva diagnostics which is using the Native AOT corert based runtime. This infrastructure will allow the necessary set of SOS commands to be written and executed across the support platforms (Windows windbg and Linux lldb).
@@ -47,16 +47,11 @@ This effort is part of the "unified extensiblity" models - where various teams a
     - [#1031](https://github.com/dotnet/diagnostics/issues/1031) "Ability to load extensions in dotnet-dump analyze". This refers to loading "sosex" and "mex" in dotnet-dump. This plan would make it easier to do this but does not actually include it.
     - [#194](https://github.com/dotnet/diagnostics/issues/194) "Implement `gcheapdiff` dotnet-dump analyze command". We haven't had a lot of feedback on whether this purposed command is useful. This issue did inspired the "multi-target" part of this plan i.e. the ability to load/analyze two dumps in dotnet-dump at the same time.
 
-## Road Map
-
-1. Create a VS host/target package allowing SOS and the extension commands to be run from VS using the Concord API.
-2. Add Linux, MacOS and Windows live snapshot targets. ClrMD already has support for them; just need to implement the target factory.
-
 ## Design
 
 The design consists of abstractions for the debugger or code hosting this infrastructure, one or more targets that represent the dump or process being targeted, one or more .NET runtimes in the target process (both desktop framework and .NET Core runtimes are supported) and various services that are avaiiable for commands, other services and the infrastructure itself. 
 
-Each hosting environment will have varing set requirements for the services needed. Other than the basic set of target, memory, and module (TBD), services can be optional. For example, hosting the native SOS code requires a richer set of interfaces than ClrMD based commands. 
+Each hosting environment will have varing set requirements for the services needed. Other than the basic set of target, memory, thread and module, most services can be optional. For example, hosting the native SOS code requires a richer set of interfaces than ClrMD based commands. 
 
 - ClrMD commands and possible analysis engine
   - Basic target info like architecture, etc.
@@ -88,17 +83,22 @@ The threading model is single-threaded mainly because native debuggers like dbge
 - IHost
   - Global services 
     - IConsoleService
+    - IConsoleFileLoggingService
     - ICommandService
     - ISymbolService
+    - IContextService
     - IDumpTargetFactory
-    - IProcessSnapshotTargetFactory
+    - IProcessAttachTargetFactory
+    - IDiagnositcLoggingService
+    - IServiceManager/IServiceContainer
   - ITarget
     - Per-target services
         - IRuntimeService
-            - IRuntime
-              - ClrInfo
-              - ClrRuntime
-              - Runtime Snapshot Parse instance
+            - IRuntimeProvider
+                - IRuntime
+                  - ClrInfo
+                  - ClrRuntime
+                  - Runtime Snapshot Parse instance
       - IMemoryService
       - IModuleService
         - IModule 
@@ -109,7 +109,9 @@ The threading model is single-threaded mainly because native debuggers like dbge
 
 ## Hosts
 
-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. 
+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.
 
 #### IHost
 
@@ -131,19 +133,27 @@ The "gcdump" target is a possible target that allows gcdump specific services an
 
 #### ITarget
 
-This interface abstracts the data target and adds value with things like per-target services and the context related state like the current thread. 
+This interface abstracts the data target, contains process architecture and platform info and adds value with things like per-target services like IMemoryService, IModuleService, IThreadService, IRuntimeService, etc.
 
 See [ITarget.cs](../../src/Microsoft.Diagnostics.DebugServices/ITarget.cs) and [target.h](../../src/SOS/inc/target.h) for details.
 
 ## Services
 
-Everything a command or another service needs are provided via a service. There are global, per-target and per-command invocation services. Services like the command, console and target service and target factories are global. Services like the thread, memory and module services are per-target. Services like the current ClrRuntime instance are per-command invocation because it could change when there are multiple runtimes in a process.
+Everything a command or another service needs are provided via a service. There are global, target, module, thread, runtime and context services. Services like the command, console and target service and target factories are global. Services like the thread, memory and module services are per-target. Services like the current ClrRuntime instance are per-runtime because it could change when there are multiple runtimes in a process.
+
+For Windbg/cdb and lldb, these services are implemented on the dbgeng API via the DebuggerServices pinvoke wrapper and interfaces.
+
+For Visual Studio, the base memory, module, thread services will be implemented on the Concord API in VS package. The rest of the services are implemented by the common code in Microsoft.Diagnostics.DebugServices.Implementation. The hardest part of this work is loading/running the native SOS and DAC modules in a 64bit environment. 
+
+Services can be registered to contain common code for commands like [ClrMDHelper](../../src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs) or host or target functionality like ISymbolService or IMemoryService.
+
+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.
 
-For Windbg/cdb, these services will be implemented on the dbgeng API.
+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.
 
-For lldb, these services will be implemented on the lldb extension API via some new pinvoke wrappers.
+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.
 
-For Visual Studio, these services will be implemented on the Concord API in VS package. The hardest part of this work is loading/running the native SOS and DAC modules in a 64bit environment. 
+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<service type>)`). 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.
 
 ### IDumpTargetFactory
 
@@ -163,16 +173,24 @@ Abstracts the console output across all the platforms.
 
 See [IConsoleService.cs](../../src/Microsoft.Diagnostics.DebugServices/IConsoleService.cs).
 
+### IConsoleFileLoggingService
+
+This service controls how the console service output is logged to a file.
+
+See [IConsoleFileLoggingService.cs](../../src/Microsoft.Diagnostics.DebugServices/IConsoleFileLoggingService.cs).
+
 ### IMemoryService
 
 Abstracts the memory related functions.
 
-There are two helper IMemoryService implementations PEImageMappingMemoryService and MetadataMappingMemoryService. They are used to wrap the base native debugger memory service implementation. 
+There are two helper IMemoryService implementations ImageMappingMemoryService and MetadataMappingMemoryService. They are used to wrap the base native debugger memory service implementation. 
 
-PEImageMappingMemoryService is used in dotnet-dump for Windows targets to mapping PE images like coreclr.dll into the memory address space the module's memory isn't present. It downloads and loads the actual module and performs the necessary fix ups.
+ImageMappingMemoryService is used in dotnet-dump for Windows targets to mapping PE, ELF and MachO images like coreclr.dll, libcoreclr.so, etc. into the memory address space the module's memory isn't present. It downloads and loads the actual module and performs the necessary fix ups.
 
 MetadataMappingMemoryService is only used for core dumps when running under lldb to map the managed assemblies metadata into address space. This is needed because the way lldb returns zero's for invalid memory for dumps generated with createdump on older runtimes (< 5.0). 
 
+To prevent recursion in these mapping services, the target service container is cloned and the memory service being wrapped replaces the base target memory service.
+
 The address sign extension plan for 32 bit processors (arm32/x86) is that address are masked on entry to the managed infrastructure from DAC or DBI callbacks or from the native SOS code in SOS.Hosting. If the native debugger that the infrastructure is hosted needs addresses to be signed extended like dbgeng, it will happen in the debugger services layer (IDebuggerService).
 
 See [IMemoryService.cs](../../src/Microsoft.Diagnostics.DebugServices/IMemoryService.cs).
@@ -191,25 +209,25 @@ One issues that may need to be addressed is that some platforms (MacOS) have non
 
 See [IModuleService.cs](../../src/Microsoft.Diagnostics.DebugServices/IModuleService.cs) and [IModule.cs](../../src/Microsoft.Diagnostics.DebugServices/IModule.cs).
 
-### ISymbolService
+### ISymbolService and ISymbolFile
 
 This service provides the symbol store services like the functionality that the static APIs in SOS.NETCore's SymbolReader.cs does now. The SOS.NETCore assembly will be removed and replaced with this symbol service implementation. Instead of directly creating delegates to static functions in this assembly, there will be a symbol service wrapper that provides these functions to the native SOS.
 
 The current implementation of the symbol downloading support in SOS.NETCore uses sync over async calls which could cause problems in more async hosts (like VS) but it hasn't caused problems in dotnet-dump so far. To fix this there may be work in the Microsoft.SymbolStore (in the symstore repo) component to expose synchronous APIs.
 
-See [ISymbolService.cs](../../src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs) for more details.
+See [ISymbolService.cs](../../src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs) and [ISymbolFile.cs](../../src/Microsoft.Diagnostics.DebugServices/ISymbolFile.cs)) for more details.
 
-### IRuntimeService/IRuntime
+### IRuntimeService/IRuntimeProvider/IRuntime
 
-This service provides the runtime instances in the target process. The IRuntime abstracts the runtime providing the ClrInfo and ClrRuntime instances from ClrMD and the Native AOT runtime snapshot parser instance in the future.
+This service provides the runtime instances in the target process. The IRuntimeService gathers all the runtimes from the possibility multiple IRuntimeProvider's in the system. There is a IRuntimeProvider for the runtimes found with CLRMD and will be one for the Native AOT snapshot parser. The IRuntime abstracts the runtime providing the ClrInfo and ClrRuntime instances from CLRMD and snapshot parser instance for Native AOT.
 
-See [IRuntimeService.cs](../../src/Microsoft.Diagnostics.DebugServices/IRuntimeService.cs), [IRuntime.cs](../../src/Microsoft.Diagnostics.DebugServices/IRuntime.cs) and [runtime.h](../../src/SOS/inc/runtime.h) for details.
+See [IRuntimeService.cs](../../src/Microsoft.Diagnostics.DebugServices/IRuntimeService.cs), [IRuntimeProvider](../../src/Microsoft.Diagnostics.DebugServices/IRuntimeProvider.cs), [IRuntime.cs](../../src/Microsoft.Diagnostics.DebugServices/IRuntime.cs) and [runtime.h](../../src/SOS/inc/runtime.h) for details.
 
-### SOSHost
+### SOSHost/SOSLibrary
 
-This service allows native SOS commands to be executed under hosts like dotnet-dump and VS. This should probably have an interface to abstract it (TBD).
+This service allows native SOS commands to be executed under hosts like dotnet-dump and VS. It provides all the native SOS hosting pinvokes and interfaces to run the native SOS commands under these debuggers. SOSLibrary is the global portion and manages loading the native SOS module and SOSHost is the per-target portion that does the actual work.
 
-Some of the native SOS's "per-target" globals will need to be queried from this infrastructure instead being set once on initialization. This includes things like the DAC module path, DBI module path, the temp directory path (since it contains the process id), etc. It will provide native versions of the IHost, ITarget and IRuntime interfaces to the native SOS to do this.
+[SOSHost](../../src/SOS/SOS.Hosting/SOSHost.cs) and [SOSLibrary](../../src/SOS/SOS.Hosting/SOSLibrary.cs) for details.
 
 ### IHostServices
 
@@ -225,11 +243,11 @@ This native interface is what the SOS.Extensions host uses to implement the abov
 
 ## Projects and Assemblies
 
-### SOS.Extensions (new)
+### SOS.Extensions
 
 This assembly implements the host, target and services for the native debuggers (dbgeng, lldb). It provides the IHostServices to the native "extensions" library which registers the IDebuggerService used by the service implementations.
 
-### The "extensions" native library (new)
+### The "extensions" native library
 
 This is the native code that interops with the managed SOS.Extensions to host the native debuggers. It sets up the managed runtime (.NET Core on Linux/MacOS or desktop on Windows) and calls the SOS.Extensions initialization entry point. It is linked into the lldbplugin on Linux/MacOS and into SOS.dll on Windows. 
 
@@ -237,25 +255,21 @@ This is the native code that interops with the managed SOS.Extensions to host th
 
 This contains the hosting support used by the dotnet-dump REPL and an eventual Visual Studio package to run native SOS commands without a native debugger like dbgeng or lldb.
 
-### SOS.NETCore (going away)
-
-This currently contains the symbol download and portable PDB source/line number support for the native SOS code. It will be replaced by the symbol service and wrappers (see ISymbolService).
-
 ### Microsoft.Diagnostics.DebugServices
 
 Contains definations and abstractions for the various services interfaces. 
 
-### Microsoft.Diagnostics.DebugServices.Implementation (new)
+### Microsoft.Diagnostics.DebugServices.Implementation
 
-Contains the common debug services implementations used by dotnet-dump and SOS.Extensions (dbgeng/lldb) hosts.
+Contains the common debug services implementations used by hosts like dotnet-dump and SOS.Extensions (dbgeng/lldb) hosts.
 
-### Microsoft.Diagnostics.ExtensionCommands (new)
+### Microsoft.Diagnostics.ExtensionCommands
 
-Contains the common commands shared with dotnet-dump and the SOS.Extensions hosts.
+Contains the common commands shared with dotnet-dump, VS and the SOS.Extensions hosts.
 
 ### Microsoft.Diagnostics.Repl
 
-The command and console service implemenations. 
+The command REPL and console service implementations. 
 
 ### dotnet-dump
 
@@ -271,3 +285,47 @@ Native SOS commands and code.
 
 On Windows, it provides the debugger services (IDebuggerServices) to SOS.Extensions and initializes the managed hosting layer via the "extensions" native library.
  
+## How to write a command
+
+Writing a new SOS command is a lot easier now that they are written C# with easy access to various services and the CLRMD API. 
+
+The first step is to decide whether you want the new command to be part of the existing set of "built-in" commands (part of the Microsoft.Diagnostics.ExtensionCommands assembly) or in your own command assembly that can be loaded when the host starts by the service manager (set the Hosts section on the details).
+
+Command and service assembly must be have a netstandard2.0 TargetFramework to run on .NET Framework hosts like VS.
+
+The next step is to create a public class that inherits from the CommandBase helper class like:
+
+```C#
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    [Command(Name = "clrmodules", Help = "Lists the managed modules in the process.")]
+    public class ClrModulesCommand : CommandBase
+    {
+        [ServiceImport(Optional = true)]
+        public ClrRuntime Runtime { get; set; }
+
+        [ServiceImport]
+        public IModuleService ModuleService { get; set; }
+
+        [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on module name (path not included).")]
+        public string ModuleName { get; set; }
+
+        [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the modules.")]
+        public bool Verbose { get; set; }
+
+        public override void Invoke()
+        {
+        }
+    }
+}
+```
+
+The "Command" attribute on the class provides the command name and help.  The "ServiceImport" attribute on the properties indicates what services are needed by the command. It can be marked as optional. The default is that the service is required. The Option attributes define the various command line option names, aliases and help. When the command is executed the service, argument and option properties are set and the "Invoke" function is called.
+
+## How to write a service
+
+TBD
+
+```C#
+```
+
index 67f1b7d1a5b76fbe7a0565dbd68ef9d4ef0570fe..792ebb6088402544b3c81aeacd998bbbe74c2333 100644 (file)
@@ -20,6 +20,6 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Linux.arm64.3.1\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Linux.arm64.3.1\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
   </files>
 </package>
index 2bddf3ab8f4ee9f396f192ad8bb6ef105a38d7bc..a589284bbe790990e0a1d59dc809624ea69fbac7 100644 (file)
@@ -20,7 +20,7 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Linux.arm64.5.0\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
-    <file src="TestAssets.Linux.arm64.5.0\1.0.246501\content\LineNums\*.*" target="content/LineNums" />
+    <file src="TestAssets.Linux.arm64.5.0\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Linux.arm64.5.0\1.0.257801\content\LineNums\*.*" target="content/LineNums" />
   </files>
 </package>
index 14af24c53ee88f14d886102dd52491acbe970f77..67da884ec48be5028612160df011e2d68704034e 100644 (file)
@@ -20,7 +20,7 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Linux.arm64.6.0\1.0.246501\content\DivZero\*.*" target="content/DivZero" />
-    <file src="TestAssets.Linux.arm64.6.0\1.0.246501\content\WebApp3\*.*" target="content/WebApp3" />
+    <file src="TestAssets.Linux.arm64.6.0\1.0.257801\content\DivZero\*.*" target="content/DivZero" />
+    <file src="TestAssets.Linux.arm64.6.0\1.0.257801\content\WebApp3\*.*" target="content/WebApp3" />
   </files>
 </package>
index ed962c4a888184d8bbeb4dcd49e9f7c407597348..332938528e4144979fcae8c68279a018e331c8bc 100644 (file)
@@ -20,6 +20,6 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Linux.x64.3.1\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Linux.x64.3.1\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
   </files>
 </package>
index 1fffb4b31d5b8f3d86c5d0771676735051b4b519..00e7b69afa41418dc6ace25602398e1a9f6fb2c6 100644 (file)
@@ -20,7 +20,7 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Linux.x64.5.0\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
-    <file src="TestAssets.Linux.x64.5.0\1.0.246501\content\LineNums\*.*" target="content/LineNums" />
+    <file src="TestAssets.Linux.x64.5.0\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Linux.x64.5.0\1.0.257801\content\LineNums\*.*" target="content/LineNums" />
   </files>
 </package>
index 1dc30e4b7bd96abf0455f67168c51b6db4593261..7df3711cb66bc056939a247876619cac6e8a8733 100644 (file)
@@ -20,7 +20,7 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Linux.x64.6.0\1.0.246501\content\DivZero\*.*" target="content/DivZero" />
-    <file src="TestAssets.Linux.x64.6.0\1.0.246501\content\WebApp3\*.*" target="content/WebApp3" />
+    <file src="TestAssets.Linux.x64.6.0\1.0.257801\content\DivZero\*.*" target="content/DivZero" />
+    <file src="TestAssets.Linux.x64.6.0\1.0.257801\content\WebApp3\*.*" target="content/WebApp3" />
   </files>
 </package>
index 8d98a46b8eac1263ac89f84e377592a1527a5c3d..e482b5d26c8c9d805826ad7a9b3a9f7ebcd31935 100644 (file)
@@ -20,6 +20,6 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Windows.x64.3.1\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Windows.x64.3.1\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
   </files>
 </package>
index 4c3957a9edfff16b244a2ea7a5b0523c44ab3cc1..e5388d5f93f56798226ea2f2ad0816f58c9e318b 100644 (file)
@@ -20,8 +20,8 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Windows.x64.5.0\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
-    <file src="TestAssets.Windows.x64.5.0\1.0.246501\content\DualRuntimes\*.*" target="content/DualRuntimes" />
-    <file src="TestAssets.Windows.x64.5.0\1.0.246501\content\LineNums\*.*" target="content/LineNums" />
+    <file src="TestAssets.Windows.x64.5.0\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Windows.x64.5.0\1.0.257801\content\DualRuntimes\*.*" target="content/DualRuntimes" />
+    <file src="TestAssets.Windows.x64.5.0\1.0.257801\content\LineNums\*.*" target="content/LineNums" />
   </files>
 </package>
index bcd07ee1dc283468f19b5859821d58641f2a6321..db21dde2a1dfd18c4952c040bd458e7d3b2d6d4e 100644 (file)
@@ -20,7 +20,7 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Windows.x64.6.0\1.0.246501\content\DivZero\*.*" target="content/DivZero" />
-    <file src="TestAssets.Windows.x64.6.0\1.0.246501\content\WebApp3\*.*" target="content/WebApp3" />
+    <file src="TestAssets.Windows.x64.6.0\1.0.257801\content\DivZero\*.*" target="content/DivZero" />
+    <file src="TestAssets.Windows.x64.6.0\1.0.257801\content\WebApp3\*.*" target="content/WebApp3" />
   </files>
 </package>
index 2a8a4513943c215a5794c40fc351b61552d47551..9cef5a47a9f7fb141193038a9c9ed1ae331f9e60 100644 (file)
@@ -20,6 +20,6 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Windows.x86.3.1\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Windows.x86.3.1\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
   </files>
 </package>
index 53c38947212b3471b9692be09e43e8c0f00b342f..4c7b5fb62eadaf8f912c50cfdf50770fe8af37c2 100644 (file)
@@ -20,8 +20,8 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Windows.x86.5.0\1.0.246501\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
-    <file src="TestAssets.Windows.x86.5.0\1.0.246501\content\DualRuntimes\*.*" target="content/DualRuntimes" />
-    <file src="TestAssets.Windows.x86.5.0\1.0.246501\content\LineNums\*.*" target="content/LineNums" />
+    <file src="TestAssets.Windows.x86.5.0\1.0.257801\content\SymbolTestApp\*.*" target="content/SymbolTestApp" />
+    <file src="TestAssets.Windows.x86.5.0\1.0.257801\content\DualRuntimes\*.*" target="content/DualRuntimes" />
+    <file src="TestAssets.Windows.x86.5.0\1.0.257801\content\LineNums\*.*" target="content/LineNums" />
   </files>
 </package>
index e6b27c53db0830899c2bac5fa305f0f4823cbe84..b9533bf6092912a55f5faac6518b50216c908746 100644 (file)
@@ -20,7 +20,7 @@
     <copyright>Copyright 2021</copyright>
   </metadata>
   <files>
-    <file src="TestAssets.Windows.x86.6.0\1.0.246501\content\DivZero\*.*" target="content/DivZero" />
-    <file src="TestAssets.Windows.x86.6.0\1.0.246501\content\WebApp3\*.*" target="content/WebApp3" />
+    <file src="TestAssets.Windows.x86.6.0\1.0.257801\content\DivZero\*.*" target="content/DivZero" />
+    <file src="TestAssets.Windows.x86.6.0\1.0.257801\content\WebApp3\*.*" target="content/WebApp3" />
   </files>
 </package>
index 235aa325de9fda3bfb7faafc91106c124b4dddaa..7ec50c2ea10c5d7df8aaf09e7f01ac88f85f3ac6 100644 (file)
@@ -1,5 +1,5 @@
 setlocal
-set ORIGINAL_VERSION=1.0.246501
+set ORIGINAL_VERSION=1.0.257801
 call ..\writexml.cmd %USERPROFILE%\.nuget\packages\testassets.linux.arm64.3.1\%ORIGINAL_VERSION%\content\SymbolTestApp\SOS.StackAndOtherTests.Heap.portable.dmp %USERPROFILE%\.nuget\packages\testassets.linux.arm64.3.1\%ORIGINAL_VERSION%\content\SymbolTestApp
 call ..\writexml.cmd %USERPROFILE%\.nuget\packages\testassets.linux.arm64.5.0\%ORIGINAL_VERSION%\content\LineNums\SOS.LineNums.Heap.dmp %USERPROFILE%\.nuget\packages\testassets.linux.arm64.5.0\%ORIGINAL_VERSION%\content\LineNums
 call ..\writexml.cmd %USERPROFILE%\.nuget\packages\testassets.linux.arm64.5.0\%ORIGINAL_VERSION%\content\SymbolTestApp\SOS.StackAndOtherTests.Heap.portable.dmp %USERPROFILE%\.nuget\packages\testassets.linux.arm64.5.0\%ORIGINAL_VERSION%\content\SymbolTestApp
index 217cfc93385b10f98c4fe5bb3fbcd528b3ea0699..03eaa8969eb2f753281ddae107b341b7be50728b 100644 (file)
@@ -3,6 +3,6 @@ set _REPOROOT_=%~dp0..\..
 set _DUMPPATH_=%1
 set _XMLPATH_=%1.xml
 set _DEBUGGEEPATH_=%2
-set DOTNET_DIAGNOSTIC_EXTENSIONS=%_REPOROOT_%\artifacts\bin\Microsoft.Diagnostics.DebugServices.UnitTests\Debug\netcoreapp3.1\Microsoft.Diagnostics.DebugServices.UnitTests.dll
-%_REPOROOT_%\.dotnet\dotnet.exe %_REPOROOT_%\artifacts\bin\dotnet-dump\Debug\netcoreapp3.1\publish\dotnet-dump.dll analyze %_DUMPPATH_% -c "setsymbolserver -directory %_DEBUGGEEPATH_%" -c "writetestdata %_XMLPATH_%" -c "exit"
+set DOTNET_DIAGNOSTIC_EXTENSIONS=%_REPOROOT_%\artifacts\bin\Microsoft.Diagnostics.DebugServices.UnitTests\Debug\net6.0\Microsoft.Diagnostics.DebugServices.UnitTests.dll
+%_REPOROOT_%\.dotnet\dotnet.exe --fx-version 6.0.8 %_REPOROOT_%\artifacts\bin\dotnet-dump\Debug\netcoreapp3.1\publish\dotnet-dump.dll analyze %_DUMPPATH_% -c "setsymbolserver -directory %_DEBUGGEEPATH_%" -c "writetestdata %_XMLPATH_%" -c "exit"
 
index 24d673a5752bcb7ad2556a24edb615eccc1969ae..88665fc6fd3791933736b6ff491eafd3e512b7f8 100755 (executable)
@@ -2,6 +2,6 @@ _REPOROOT_=../..
 _DUMPPATH_=$1
 _XMLPATH_=$1.xml
 _DEBUGGEEPATH_=$2
-export DOTNET_DIAGNOSTIC_EXTENSIONS=$_REPOROOT_/artifacts/bin/Microsoft.Diagnostics.DebugServices.UnitTests/Debug/netcoreapp3.1/Microsoft.Diagnostics.DebugServices.UnitTests.dll
-$_REPOROOT_/.dotnet/dotnet $_REPOROOT_/artifacts/bin/dotnet-dump/Debug/netcoreapp3.1/publish/dotnet-dump.dll analyze $_DUMPPATH_ -c "setsymbolserver -directory $_DEBUGGEEPATH_" -c "writetestdata $_XMLPATH_" -c "exit"
+export DOTNET_DIAGNOSTIC_EXTENSIONS=$_REPOROOT_/artifacts/bin/Microsoft.Diagnostics.DebugServices.UnitTests/Debug/net6.0/Microsoft.Diagnostics.DebugServices.UnitTests.dll
+$_REPOROOT_/.dotnet/dotnet --fx-version 6.0.8 $_REPOROOT_/artifacts/bin/dotnet-dump/Debug/netcoreapp3.1/publish/dotnet-dump.dll analyze $_DUMPPATH_ -c "setsymbolserver -directory $_DEBUGGEEPATH_" -c "writetestdata $_XMLPATH_" -c "exit"
 
index f8198b89ddadecb25307318068ca65b170c654bb..63cf2dd35edcc7b5bf0701d724b3a6c1485038b4 100644 (file)
@@ -3,6 +3,6 @@ set _REPOROOT_=%~dp0..\..
 set _DUMPPATH_=%1
 set _XMLPATH_=%1.xml
 set _DEBUGGEEPATH_=%2
-set DOTNET_DIAGNOSTIC_EXTENSIONS=%_REPOROOT_%\artifacts\bin\Microsoft.Diagnostics.DebugServices.UnitTests\Debug\netcoreapp3.1\Microsoft.Diagnostics.DebugServices.UnitTests.dll
-%_REPOROOT_%\.dotnet\x86\dotnet.exe %_REPOROOT_%\artifacts\bin\dotnet-dump\Debug\netcoreapp3.1\publish\dotnet-dump.dll analyze %_DUMPPATH_% -c "setsymbolserver -directory %_DEBUGGEEPATH_%" -c "writetestdata %_XMLPATH_%" -c "exit"
+set DOTNET_DIAGNOSTIC_EXTENSIONS=%_REPOROOT_%\artifacts\bin\Microsoft.Diagnostics.DebugServices.UnitTests\Debug\net6.0\Microsoft.Diagnostics.DebugServices.UnitTests.dll
+%_REPOROOT_%\.dotnet\x86\dotnet.exe --fx-version 6.0.8 %_REPOROOT_%\artifacts\bin\dotnet-dump\Debug\netcoreapp3.1\publish\dotnet-dump.dll analyze %_DUMPPATH_% -c "setsymbolserver -directory %_DEBUGGEEPATH_%" -c "writetestdata %_XMLPATH_%" -c "exit"
 
index 7585f33b9011cc224b63cac75abeff2555c75fb8..26449ca643082c870de251cdb40bb79219b53bc8 100644 (file)
@@ -162,6 +162,12 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// </summary>
         public IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases));
 
+        /// <summary>
+        /// Add the commands and aliases attributes found in the type.
+        /// </summary>
+        /// <param name="type">Command type to search</param>
+        public void AddCommands(Type type) => AddCommands(type, factory: null);
+
         /// <summary>
         /// Add the commands and aliases attributes found in the type.
         /// </summary>
@@ -184,7 +190,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                         {
                             if (factory == null)
                             {
-                                factory = (services) => Utilities.InvokeConstructor(type, services, optional: true);
+                                factory = (services) => Utilities.CreateInstance(type, services);
                             }
                             CreateCommand(baseType, commandAttribute, factory);
                         }
@@ -239,11 +245,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                             option.AddAlias(alias);
                         }
                     }
-                    else
-                    {
-                        // If not an option, add as just a settable properties
-                        properties.Add((property, null));
-                    }
                 }
             }
 
@@ -373,11 +374,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services)
             {
                 object instance = _factory(services);
-                SetProperties(context, parser, services, instance);
-                Utilities.Invoke(methodInfo, instance, services, optional: true);
+                SetProperties(context, parser, instance);
+                Utilities.Invoke(methodInfo, instance, services);
             }
 
-            private void SetProperties(InvocationContext context, Parser parser, IServiceProvider services, object instance)
+            private void SetProperties(InvocationContext context, Parser parser, object instance)
             {
                 ParseResult defaultParseResult = null;
 
@@ -409,14 +410,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                             }
                         }
                     }
-                    else
-                    { 
-                        Type propertyType = property.Property.PropertyType;
-                        object service = services.GetService(propertyType);
-                        if (service != null) {
-                            value = service;
-                        }
-                    }
 
                     property.Property.SetValue(instance, value);
                 }
@@ -510,8 +503,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             public LocalConsole(IServiceProvider services)
             {
                 Services = services;
-                Out = new StandardStreamWriter((text) => ConsoleService.Write(text));
-                Error = new StandardStreamWriter((text) => ConsoleService.WriteError(text));
+                Out = new StandardStreamWriter(ConsoleService.Write);
+                Error = new StandardStreamWriter(ConsoleService.WriteError);
             }
 
             internal readonly IServiceProvider Services;
index a2836da09682c4aad04ccdc0a41f83b05ba0a069..36ebfca08e5104a2405e22eeb8e8e7941389c8fe 100644 (file)
@@ -13,28 +13,23 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// </summary>
     public class ContextService : IContextService
     {
-        protected readonly IHost Host;
+        protected readonly IHost _host;
+        protected readonly ServiceContainer _serviceContainer;
         private ITarget _currentTarget;
         private IThread _currentThread;
         private IRuntime _currentRuntime;
 
-        public readonly ServiceProvider ServiceProvider;
-
         public ContextService(IHost host)
         {
-            Host = host;
+            _host = host;
+            var parent = new ContextServiceProvider(this);
+            _serviceContainer = host.Services.GetService<IServiceManager>().CreateServiceContainer(ServiceScope.Context, parent);
 
-            ServiceProvider = new ServiceProvider(new Func<IServiceProvider>[] {
-                // First check the current runtime for the service
-                () => GetCurrentRuntime()?.Services,
-                // If there is no target, then provide just the global services
-                () => GetCurrentTarget()?.Services ?? host.Services
+            // Clear the current context when a target is flushed or destroyed
+            host.OnTargetCreate.Register((target) => {
+                target.OnFlushEvent.Register(() => ClearCurrentTarget(target));
+                target.OnDestroyEvent.Register(() => ClearCurrentTarget(target));
             });
-
-            // These services depend on no caching
-            ServiceProvider.AddServiceFactoryWithNoCaching<ITarget>(GetCurrentTarget);
-            ServiceProvider.AddServiceFactoryWithNoCaching<IThread>(GetCurrentThread);
-            ServiceProvider.AddServiceFactoryWithNoCaching<IRuntime>(GetCurrentRuntime);
         }
 
         #region IContextService
@@ -43,7 +38,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// Current context service provider. Contains the current ITarget, IThread 
         /// and IRuntime instances along with all per target and global services.
         /// </summary>
-        public IServiceProvider Services => ServiceProvider;
+        public IServiceProvider Services => _serviceContainer;
 
         /// <summary>
         /// Fires anytime the current context changes.
@@ -57,7 +52,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <exception cref="DiagnosticsException">invalid target id</exception>
         public void SetCurrentTarget(int targetId)
         {
-            ITarget target = Host.EnumerateTargets().FirstOrDefault((target) => target.Id == targetId);
+            ITarget target = _host.EnumerateTargets().SingleOrDefault((target) => target.Id == targetId);
             if (target is null) {
                 throw new DiagnosticsException($"Invalid target id {targetId}");
             }
@@ -88,7 +83,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <exception cref="DiagnosticsException">invalid runtime id</exception>
         public void SetCurrentRuntime(int runtimeId)
         {
-            IRuntime runtime = RuntimeService?.EnumerateRuntimes().FirstOrDefault((runtime) => runtime.Id == runtimeId);
+            IRuntime runtime = RuntimeService?.EnumerateRuntimes().SingleOrDefault((runtime) => runtime.Id == runtimeId);
             if (runtime is null) {
                 throw new DiagnosticsException($"Invalid runtime id {runtimeId}");
             }
@@ -105,20 +100,32 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <summary>
         /// Returns the current target.
         /// </summary>
-        public ITarget GetCurrentTarget() => _currentTarget ??= Host.EnumerateTargets().FirstOrDefault();
+        protected virtual ITarget GetCurrentTarget() => _currentTarget ??= _host.EnumerateTargets().FirstOrDefault();
+
+        /// <summary>
+        /// Clears the context service state if the target is current
+        /// </summary>
+        /// <param name="target"></param>
+        private void ClearCurrentTarget(ITarget target)
+        {
+            if (IsTargetEqual(target, _currentTarget))
+            {
+                SetCurrentTarget(null);
+            }
+        }
 
         /// <summary>
-        /// Allows hosts to set the initial current target
+        /// Allows hosts to set the current target. Fires the context change event if the current target has changed.
         /// </summary>
         /// <param name="target"></param>
-        public void SetCurrentTarget(ITarget target)
+        public virtual void SetCurrentTarget(ITarget target)
         {
             if (!IsTargetEqual(target, _currentTarget))
             {
                 _currentTarget = target;
                 _currentThread = null;
                 _currentRuntime = null;
-                ServiceProvider.FlushServices();
+                _serviceContainer.DisposeServices();
                 OnContextChange.Fire();
             }
         }
@@ -126,10 +133,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <summary>
         /// Returns the current thread.
         /// </summary>
-        public virtual IThread GetCurrentThread() => _currentThread ??= ThreadService?.EnumerateThreads().FirstOrDefault();
+        protected virtual IThread GetCurrentThread() => _currentThread ??= ThreadService?.EnumerateThreads().FirstOrDefault();
 
         /// <summary>
-        /// Allows hosts to set the initial current thread
+        /// Allows hosts to set the current thread. Fires the context change event if the current thread has changed.
         /// </summary>
         /// <param name="thread"></param>
         public virtual void SetCurrentThread(IThread thread)
@@ -137,7 +144,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             if (!IsThreadEqual(thread, _currentThread))
             {
                 _currentThread = thread;
-                ServiceProvider.FlushServices();
+                _serviceContainer.DisposeServices();
                 OnContextChange.Fire();
             }
         }
@@ -145,7 +152,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <summary>
         /// Find the current runtime.
         /// </summary>
-        public IRuntime GetCurrentRuntime()
+        protected virtual IRuntime GetCurrentRuntime()
         {
             if (_currentRuntime is null)
             {
@@ -184,14 +191,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         }
 
         /// <summary>
-        /// Allows hosts to set the initial current runtime 
+        /// Allows hosts to set the current runtime. Fires the context change event if the current thread has changed.
         /// </summary>
-        public void SetCurrentRuntime(IRuntime runtime)
+        public virtual void SetCurrentRuntime(IRuntime runtime)
         {
             if (!IsRuntimeEqual(runtime, _currentRuntime))
             {
                 _currentRuntime = runtime;
-                ServiceProvider.FlushServices();
+                _serviceContainer.DisposeServices();
                 OnContextChange.Fire();
             }
         }
@@ -223,5 +230,77 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         protected IThreadService ThreadService => GetCurrentTarget()?.Services.GetService<IThreadService>();
 
         protected IRuntimeService RuntimeService => GetCurrentTarget()?.Services.GetService<IRuntimeService>();
+
+        /// <summary>
+        /// Special context service parent forwarding wrapper
+        /// </summary>
+        private class ContextServiceProvider : IServiceProvider
+        {
+            private readonly ContextService _contextService;
+
+            /// <summary>
+            /// Create a special context service provider parent that forwards to the current runtime, target or host
+            /// </summary>
+            public ContextServiceProvider(ContextService contextService)
+            {
+                _contextService = contextService;
+            }
+
+            /// <summary>
+            /// Returns the instance of the service or returns null if service doesn't exist
+            /// </summary>
+            /// <param name="type">service type</param>
+            /// <returns>service instance or null</returns>
+            public object GetService(Type type)
+            {
+                if (type == typeof(IRuntime))
+                {
+                    return _contextService.GetCurrentRuntime();
+                }
+                else if (type == typeof(IThread))
+                {
+                    return _contextService.GetCurrentThread();
+                }
+                else if (type == typeof(ITarget))
+                {
+                    return _contextService.GetCurrentTarget();
+                }
+                // Check the current runtime (if exists) for the service.
+                IRuntime currentRuntime = _contextService.GetCurrentRuntime();
+                if (currentRuntime is not null)
+                {
+                    // This will chain to the target then the global services if not found in the current runtime
+                    object service = currentRuntime.Services.GetService(type);
+                    if (service is not null)
+                    {
+                        return service;
+                    }
+                }
+                // Check the current thread (if exists) for the service.
+                IThread currentThread = _contextService.GetCurrentThread();
+                if (currentThread is not null)
+                {
+                    // This will chain to the target then the global services if not found in the current thread
+                    object service = currentThread.Services.GetService(type);
+                    if (service is not null)
+                    {
+                        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);
+            }
+        }
     }
 }
index 27fe813105350ae2a12c2c0f8cc4af8cb4e4e869..f7b998e394e83aeb5126a909f5fe46619c23b0eb 100644 (file)
@@ -16,13 +16,20 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// <summary>
     /// ClrMD runtime service implementation
     /// </summary>
-    internal class DataReader : IDataReader
+    [ServiceExport(Type = typeof(IDataReader), Scope = ServiceScope.Target)]
+    public class DataReader : IDataReader
     {
         private readonly ITarget _target;
         private IEnumerable<ModuleInfo> _modules;
-        private IModuleService _moduleService;
-        private IThreadService _threadService;
-        private IMemoryService _memoryService;
+
+        [ServiceImport]
+        private IModuleService ModuleService { get; set; }
+
+        [ServiceImport]
+        private IMemoryService MemoryService { get; set; }
+
+        [ServiceImport]
+        private IThreadService ThreadService { get; set; }
 
         public DataReader(ITarget target)
         {
@@ -106,12 +113,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
         #endregion
 
-        private IModuleService ModuleService => _moduleService ??= _target.Services.GetService<IModuleService>();
-
-        private IMemoryService MemoryService => _memoryService ??= _target.Services.GetService<IMemoryService>();
-
-        private IThreadService ThreadService => _threadService ??= _target.Services.GetService<IThreadService>();
-
         private class DataReaderModule : ModuleInfo
         {
             private readonly IModule _module;
index 584f26cc3b8931f9d1c8eb655b79ba406dc85163..07bf824a2c8a91e78557769c7e94c3dc6de828f5 100644 (file)
@@ -2,50 +2,61 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using Microsoft.FileFormats;
 using Microsoft.FileFormats.ELF;
 using System;
-using System.Diagnostics;
-using System.IO;
+using System.Runtime.InteropServices;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
     /// <summary>
-    /// Disposable ELFFile wrapper around the module file.
+    /// ELFModule service that provides downloaded module ELFFile wrapper.
     /// </summary>
-    public class ELFModule : ELFFile
+    public class ELFModule : IDisposable
     {
+        private readonly IModule _module;
+        private readonly ISymbolService _symbolService;
+        private readonly IDisposable _onChangeEvent;
+        private ELFFile _elfFile;
+
         /// <summary>
-        /// Opens and returns an ELFFile instance from the local file path
+        /// Creates a ELFModule service instance of the downloaded or local (if exists) module file.
         /// </summary>
-        /// <param name="filePath">ELF file to open</param>
-        /// <returns>ELFFile instance or null</returns>
-        public static ELFModule OpenFile(string filePath)
+        [ServiceExport(Scope = ServiceScope.Module)]
+        public static ELFModule CreateELFModule(IModule module, ISymbolService symbolService)
         {
-            Stream stream = Utilities.TryOpenFile(filePath);
-            if (stream is not null)
+            if (module.Target.OperatingSystem == OSPlatform.Linux)
             {
-                try
-                {
-                    ELFModule elfModule = new(stream);
-                    if (!elfModule.IsValid())
-                    {
-                        Trace.TraceError($"OpenELFFile: not a valid file");
-                        return null;
-                    }
-                    return elfModule;
-                }
-                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+                if (!module.BuildId.IsDefaultOrEmpty)
                 {
-                    Trace.TraceError($"OpenELFFile: exception {ex.Message}");
+                    return new ELFModule(module, symbolService);
                 }
             }
             return null;
         }
 
-        public ELFModule(Stream stream) :
-            base(new StreamAddressSpace(stream), position: 0, isDataSourceVirtualAddressSpace: false)
+        private ELFModule(IModule module, ISymbolService symbolService)
+        {
+            _module = module;
+            _symbolService = symbolService;
+            _onChangeEvent = symbolService.OnChangeEvent.Register(() => {
+                _elfFile?.Dispose();
+                _elfFile = null; 
+            });
+        }
+
+        public ELFFile GetELFFile()
+        {
+            if (_elfFile == null)
+            {
+                _elfFile = Utilities.OpenELFFile(_symbolService.DownloadModuleFile(_module));
+            }
+            return _elfFile;
+        }
+
+        public void Dispose()
         {
+            _elfFile?.Dispose();
+            _onChangeEvent.Dispose();
         }
     }
-}
\ No newline at end of file
+}
index 1ceffe6044e8afaa9de52b0d3e7c45eb1101461f..5fcf99e6b01ec4faf8aedea2475aedbb70ba6a41 100644 (file)
@@ -15,30 +15,54 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// <summary>
     /// Memory service wrapper that maps and fixes up PE module on read memory errors.
     /// </summary>
-    internal class ImageMappingMemoryService : IMemoryService
+    public class ImageMappingMemoryService : IMemoryService, IDisposable
     {
+        private readonly ServiceContainer _serviceContainer;
         private readonly IMemoryService _memoryService;
         private readonly IModuleService _moduleService;
         private readonly MemoryCache _memoryCache;
+        private readonly IDisposable _onChangeEvent;
         private readonly HashSet<ulong> _recursionProtection;
 
         /// <summary>
-        /// The PE and ELF image mapping memory service. This service assumes that the managed PE 
-        /// assemblies are in the module service's list. This is true for dbgeng, dotnet-dump but not
-        /// true for lldb (only native modules are provided).
+        /// The PE, ELF and MacOS image mapping memory service. For the dotnet-dump linux dump reader and
+        /// dbgeng the native module service providers managed and modules, but under lldb only native 
+        /// modules are provided. The "managed" flag is for those later cases.
         /// </summary>
-        /// <param name="target">target instance</param>
+        /// <param name="container">service container</param>
         /// <param name="memoryService">memory service to wrap</param>
-        internal ImageMappingMemoryService(ITarget target, IMemoryService memoryService)
+        /// <param name="managed">if true, map managed modules, else native</param>
+        public ImageMappingMemoryService(ServiceContainer container, IMemoryService memoryService, bool managed)
         {
+            _serviceContainer = container;
+            container.AddService(memoryService);
+
             _memoryService = memoryService;
-            _moduleService = target.Services.GetService<IModuleService>();
+            _moduleService = managed ? new ManagedImageMappingModuleService(container) : container.GetService<IModuleService>();
             _memoryCache = new MemoryCache(ReadMemoryFromModule);
             _recursionProtection = new HashSet<ulong>();
-            target.OnFlushEvent.Register(_memoryCache.FlushCache);
-            target.DisposeOnDestroy(target.Services.GetService<ISymbolService>()?.OnChangeEvent.Register(_memoryCache.FlushCache));
+
+            ITarget target = container.GetService<ITarget>();
+            target.OnFlushEvent.Register(Flush);
+
+            ISymbolService symbolService = container.GetService<ISymbolService>();
+            _onChangeEvent = symbolService?.OnChangeEvent.Register(Flush);
         }
 
+        public void Dispose() 
+        {
+            Flush();
+            _onChangeEvent?.Dispose();
+            _serviceContainer.RemoveService(typeof(IMemoryService));
+            _serviceContainer.DisposeServices();
+            if (_memoryService is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
+        } 
+
+        protected void Flush() => _memoryCache.FlushCache();
+
         #region IMemoryService
 
         /// <summary>
@@ -95,18 +119,22 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         private byte[] ReadMemoryFromModule(ulong address, int bytesRequested)
         {
             Debug.Assert((address & ~_memoryService.SignExtensionMask()) == 0);
-            IModule module = _moduleService.GetModuleFromAddress(address);
-            if (module != null)
+
+            // Default is to cache errors
+            byte[] data = Array.Empty<byte>();
+
+            // Recursion can happen in the case where the PE, ELF or MachO headers (in the module.Services.GetService<>() calls)
+            // used to get the timestamp/filesize or build id are not in the dump.
+            if (!_recursionProtection.Contains(address))
             {
-                // Recursion can happen in the case where the PE, ELF or MachO headers (in the module.Services.GetService<>() calls)
-                // used to get the timestamp/filesize or build id are not in the dump.
-                if (!_recursionProtection.Contains(address))
+                _recursionProtection.Add(address);
+                try
                 {
-                    _recursionProtection.Add(address);
-                    try
+                    IModule module = _moduleService.GetModuleFromAddress(address);
+                    if (module != null)
                     {
                         // We found a module that contains the memory requested. Now find or download the PE image.
-                        PEReader reader = module.Services.GetService<PEReader>();
+                        PEReader reader = module.Services.GetService<PEModule>()?.GetPEReader();
                         if (reader is not null)
                         {
                             int rva = (int)(address - module.ImageBase);
@@ -137,8 +165,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
                             try
                             {
-                                byte[] data = null;
-
                                 // Read the memory from the PE image found/downloaded above
                                 PEMemoryBlock block = reader.GetEntireImage();
                                 if (rva < block.Length)
@@ -154,8 +180,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                                         Trace.TraceError($"ReadMemoryFromModule: FAILED address {address:X16} rva {rva:X8} {module.FileName}");
                                     }
                                 }
-                                
-                                return data;
                             }
                             catch (Exception ex) when (ex is BadImageFormatException || ex is InvalidOperationException || ex is IOException)
                             {
@@ -165,11 +189,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                         else
                         {
                             // Find or download the ELF image, if one.
-                            Reader virtualAddressReader = module.Services.GetService<ELFModule>()?.VirtualAddressReader;
+                            Reader virtualAddressReader = module.Services.GetService<ELFModule>()?.GetELFFile()?.VirtualAddressReader;
                             if (virtualAddressReader is null)
                             {
                                 // Find or download the MachO image, if one.
-                                virtualAddressReader = module.Services.GetService<MachOModule>()?.VirtualAddressReader;
+                                virtualAddressReader = module.Services.GetService<MachOModule>()?.GetMachOFile()?.VirtualAddressReader;
                             }
                             if (virtualAddressReader is not null)
                             {
@@ -181,14 +205,13 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 #if TRACE_VERBOSE
                                     Trace.TraceInformation($"ReadMemoryFromModule: address {address:X16} rva {rva:X16} size {bytesRequested:X8} in ELF or MachO file {module.FileName}");
 #endif
-                                    byte[] data = new byte[bytesRequested];
+                                    data = new byte[bytesRequested];
                                     uint read = virtualAddressReader.Read(rva, data, 0, (uint)bytesRequested);
                                     if (read == 0)
                                     {
                                         Trace.TraceError($"ReadMemoryFromModule: FAILED address {address:X16} rva {rva:X16} {module.FileName}");
-                                        data = null;
+                                        data = Array.Empty<byte>();
                                     }
-                                    return data;
                                 }
                                 catch (Exception ex) when (ex is BadInputFormatException || ex is InvalidVirtualAddressException)
                                 {
@@ -197,17 +220,17 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                             }
                         }
                     }
-                    finally
-                    {
-                        _recursionProtection.Remove(address);
-                    }
                 }
-                else
+                finally
                 {
-                    Trace.TraceError($"ReadMemoryFromModule: recursion: address {address:X16} size {bytesRequested:X8} {module.FileName}");
+                    _recursionProtection.Remove(address);
                 }
             }
-            return null;
+            else
+            {
+                throw new InvalidOperationException($"ReadMemoryFromModule: recursion: address {address:X16} size {bytesRequested:X8}");
+            }
+            return data;
         }
 
         enum BaseRelocationType
@@ -235,7 +258,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     // Read IMAGE_BASE_RELOCATION struct
                     int virtualAddress = blob.ReadInt32();
                     int sizeOfBlock = blob.ReadInt32();
-
+                    if (sizeOfBlock <= 0) {
+                        break;
+                    }
                     // Each relocation block covers 4K
                     if (dataVA >= virtualAddress && dataVA < (virtualAddress + 4096))
                     {
index 467f52c8f222dbb49a745fa23ac1c719818a7cd7..2d121cb88f896c40da171181115366a28bd79b35 100644 (file)
@@ -2,50 +2,61 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using Microsoft.FileFormats;
 using Microsoft.FileFormats.MachO;
 using System;
-using System.Diagnostics;
-using System.IO;
+using System.Runtime.InteropServices;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
     /// <summary>
-    /// Disposable MachOFile wrapper around the module file.
+    /// MachOModule service that provides downloaded module MachOFile wrapper.
     /// </summary>
-    public class MachOModule : MachOFile
+    public class MachOModule : IDisposable
     {
+        private readonly IModule _module;
+        private readonly ISymbolService _symbolService;
+        private readonly IDisposable _onChangeEvent;
+        private MachOFile _machOFile;
+
         /// <summary>
-        /// Opens and returns an MachOFile instance from the local file path
+        /// Creates a MachOModule service instance of the downloaded or local (if exists) module file.
         /// </summary>
-        /// <param name="filePath">MachO file to open</param>
-        /// <returns>MachOFile instance or null</returns>
-        public static MachOModule OpenFile(string filePath)
+        [ServiceExport(Scope = ServiceScope.Module)]
+        public static MachOModule CreateMachOModule(ISymbolService symbolService, IModule module)
         {
-            Stream stream = Utilities.TryOpenFile(filePath);
-            if (stream is not null)
+            if (module.Target.OperatingSystem == OSPlatform.OSX)
             {
-                try
-                {
-                    var machoModule = new MachOModule(stream);
-                    if (!machoModule.IsValid())
-                    {
-                        Trace.TraceError($"OpenMachOFile: not a valid file");
-                        return null;
-                    }
-                    return machoModule;
-                }
-                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+                if (!module.BuildId.IsDefaultOrEmpty)
                 {
-                    Trace.TraceError($"OpenMachOFile: exception {ex.Message}");
+                    return new MachOModule(module, symbolService);
                 }
             }
             return null;
         }
 
-        public MachOModule(Stream stream) :
-            base(new StreamAddressSpace(stream), position: 0, dataSourceIsVirtualAddressSpace: false)
+        private MachOModule(IModule module, ISymbolService symbolService)
+        {
+            _module = module;
+            _symbolService = symbolService;
+            _onChangeEvent = symbolService.OnChangeEvent.Register(() => {
+                _machOFile?.Dispose();
+                _machOFile = null; 
+            });
+        }
+
+        public MachOFile GetMachOFile()
+        {
+            if (_machOFile == null)
+            {
+                _machOFile = Utilities.OpenMachOFile(_symbolService.DownloadModuleFile(_module));
+            }
+            return _machOFile;
+        }
+
+        public void Dispose()
         {
+            _machOFile?.Dispose();
+            _onChangeEvent.Dispose();
         }
     }
 }
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ManagedImageMappingModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ManagedImageMappingModuleService.cs
new file mode 100644 (file)
index 0000000..f4edd38
--- /dev/null
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    /// <summary>
+    /// Module service implementation for managed image mapping
+    /// </summary>
+    public class ManagedImageMappingModuleService : ModuleService
+    {
+        private readonly IRuntimeService _runtimeService;
+
+        public ManagedImageMappingModuleService(IServiceProvider services)
+            : base(services)
+        {
+            _runtimeService = services.GetService<IRuntimeService>();
+        }
+
+        /// <summary>
+        /// Get/create the modules dictionary.
+        /// </summary>
+        protected override Dictionary<ulong, IModule> GetModulesInner()
+        {
+            var modules = new Dictionary<ulong, IModule>();
+            int moduleIndex = 0;
+
+            IEnumerable<IRuntime> runtimes = _runtimeService.EnumerateRuntimes();
+            if (runtimes.Any())
+            {
+                foreach (IRuntime runtime in runtimes)
+                {
+                    ClrRuntime clrRuntime = runtime.Services.GetService<ClrRuntime>();
+                    if (clrRuntime is not null)
+                    {
+                        foreach (ClrModule clrModule in clrRuntime.EnumerateModules())
+                        {
+                            ModuleFromAddress module = new(this, moduleIndex, clrModule.ImageBase, clrModule.Size, clrModule.Name);
+                            try
+                            {
+                                modules.Add(module.ImageBase, module);
+                                moduleIndex++;
+                            }
+                            catch (ArgumentException)
+                            {
+                                Trace.TraceError($"GetModulesInner(): duplicate module base '{module}' dup '{modules[module.ImageBase]}'");
+                            }
+                        }
+                    }
+                }
+            }
+
+            return modules;
+        }
+    }
+}
index 19b382ee8052e90fa095b4a6a51c6bed5cdf830d..57aa6adfe36de818b2cbd687d0fd4f458197e108 100644 (file)
@@ -20,26 +20,47 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// that older (less than 5.0) createdumps generate  so it needs this special 
     /// metadata  mapping memory service.
     /// </summary>
-    public class MetadataMappingMemoryService : IMemoryService
+    public class MetadataMappingMemoryService : IMemoryService, IDisposable
     {
-        private readonly ITarget _target;
+        private readonly ServiceContainer _serviceContainer;
         private readonly IMemoryService _memoryService;
+        private readonly IRuntimeService _runtimeService;
+        private readonly ISymbolService _symbolService;
+        private readonly IDisposable _onChangeEvent;
         private bool _regionInitialized;
         private ImmutableArray<MetadataRegion> _regions;
-        private IRuntimeService _runtimeService;
-        private ISymbolService _symbolService;
 
         /// <summary>
         /// Memory service constructor
         /// </summary>
-        /// <param name="target">target instance</param>
+        /// <param name="container">service container</param>
         /// <param name="memoryService">memory service to wrap</param>
-        public MetadataMappingMemoryService(ITarget target, IMemoryService memoryService)
+        public MetadataMappingMemoryService(ServiceContainer container, IMemoryService memoryService)
         {
-            _target = target;
+            _serviceContainer = container;
+            container.AddService(memoryService);
+
             _memoryService = memoryService;
+            _runtimeService = container.GetService<IRuntimeService>();
+            _symbolService = container.GetService<ISymbolService>();    
+
+            ITarget target = container.GetService<ITarget>();
             target.OnFlushEvent.Register(Flush);
-            target.DisposeOnDestroy(SymbolService?.OnChangeEvent.Register(Flush));
+
+            ISymbolService symbolService = container.GetService<ISymbolService>();
+            _onChangeEvent = symbolService?.OnChangeEvent.Register(Flush);
+        }
+
+        public void Dispose()
+        {
+            Flush();
+            _onChangeEvent?.Dispose();
+            _serviceContainer.RemoveService(typeof(IMemoryService));
+            _serviceContainer.DisposeServices();
+            if (_memoryService is IDisposable disposable)
+            {
+                disposable.Dispose();
+            }
         }
 
         /// <summary>
@@ -103,16 +124,16 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 // Need to set this before enumerating the runtimes to prevent reentrancy
                 _regionInitialized = true;
 
-                var runtimes = RuntimeService.EnumerateRuntimes();
+                var runtimes = _runtimeService.EnumerateRuntimes();
                 if (runtimes.Any())
                 {
                     foreach (IRuntime runtime in runtimes)
                     {
                         Trace.TraceInformation($"FindRegion: initializing regions for runtime #{runtime.Id}");
                         ClrRuntime clrRuntime = runtime.Services.GetService<ClrRuntime>();
-                        if (clrRuntime != null)
+                        if (clrRuntime is not null)
                         {
-                            Trace.TraceInformation($"FindRegion: initializing regions for CLR runtime #{runtime.Id}");
+                            Trace.TraceInformation($"FindRegion: initializing regions for ClrRuntime #{runtime.Id}");
                             _regions = clrRuntime.EnumerateModules()
                                 .Where((module) => module.MetadataAddress != 0 && module.IsPEFile && !module.IsDynamic)
                                 .Select((module) => new MetadataRegion(this, module))
@@ -167,7 +188,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 PEFile peFile = new(new StreamAddressSpace(stream), isVirtual);
                 if (peFile.IsValid())
                 {
-                    metadata = SymbolService.GetMetadata(module.Name, peFile.Timestamp, peFile.SizeOfImage);
+                    metadata = _symbolService.GetMetadata(module.Name, peFile.Timestamp, peFile.SizeOfImage);
                 }
                 else
                 {
@@ -181,10 +202,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return metadata;
         }
 
-        private IRuntimeService RuntimeService => _runtimeService ??= _target.Services.GetService<IRuntimeService>();
-
-        private ISymbolService SymbolService => _symbolService ??= _target.Services.GetService<ISymbolService>();
-
         class MetadataRegion : IComparable<MetadataRegion>
         {
             private readonly MetadataMappingMemoryService _memoryService;
index 20ba0b92b78d7080d4d956cd3a7c2677c2da4708..7dae04c6b3586eb716cb331f0693cfb5c1163dbe 100644 (file)
@@ -5,14 +5,12 @@
 using Microsoft.Diagnostics.Runtime;
 using Microsoft.FileFormats;
 using Microsoft.FileFormats.ELF;
-using Microsoft.FileFormats.MachO;
 using Microsoft.FileFormats.PE;
 using System;
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Diagnostics;
 using System.IO;
-using System.Reflection.PortableExecutable;
 using System.Runtime.InteropServices;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
@@ -30,93 +28,51 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             IsManaged = 0x02,
             IsFileLayout = 0x04,
             IsLoadedLayout = 0x08,
-            InitializePEInfo = 0x10,
-            InitializeVersion = 0x20,
-            InitializeProductVersion = 0x40,
-            InitializeSymbolFileName = 0x80
+            InitializeVersion = 0x10,
+            InitializeProductVersion = 0x20,
+            InitializeSymbolFileName = 0x40
         }
 
-        private readonly IDisposable _onChangeEvent;
         private Flags _flags;
         private IEnumerable<PdbFileInfo> _pdbFileInfos;
-        protected ImmutableArray<byte> _buildId;
-        private PEFile _peFile;
         private string _symbolFileName;
 
-        public readonly ServiceProvider ServiceProvider;
+        protected ImmutableArray<byte> _buildId;
+        protected readonly ServiceContainer _serviceContainer;
 
-        public Module(ITarget target)
+        public Module(IServiceProvider services)
         {
-            ServiceProvider = new ServiceProvider();
-            ServiceProvider.AddServiceFactoryWithNoCaching<PEFile>(() => GetPEInfo());
-            ServiceProvider.AddService<IExportSymbols>(this);
-
-            ServiceProvider.AddServiceFactory<PEReader>(() => {
-                if (!IndexTimeStamp.HasValue || !IndexFileSize.HasValue) {
-                    return null;
-                }
-                return Utilities.OpenPEReader(ModuleService.SymbolService.DownloadModuleFile(this));
-            });
-
-            if (target.OperatingSystem == OSPlatform.Linux) 
-            {
-                ServiceProvider.AddServiceFactory<ELFModule>(() => {
-                    if (BuildId.IsDefaultOrEmpty) {
-                        return null;
-                    }
-                    return ELFModule.OpenFile(ModuleService.SymbolService.DownloadModuleFile(this));
-                });
-                ServiceProvider.AddServiceFactory<ELFFile>(() => {
-                    Stream stream = ModuleService.MemoryService.CreateMemoryStream();
-                    var elfFile = new ELFFile(new StreamAddressSpace(stream), ImageBase, true);
-                    return elfFile.IsValid() ? elfFile : null;
-                });
-            }
-
-            if (target.OperatingSystem == OSPlatform.OSX) 
-            {
-                ServiceProvider.AddServiceFactory<MachOModule>(() => {
-                    if (BuildId.IsDefaultOrEmpty) {
-                        return null;
-                    }
-                    return MachOModule.OpenFile(ModuleService.SymbolService.DownloadModuleFile(this));
-                });
-                ServiceProvider.AddServiceFactory<MachOFile>(() => {
-                    Stream stream = ModuleService.MemoryService.CreateMemoryStream();
-                    var machoFile = new MachOFile(new StreamAddressSpace(stream), ImageBase, true);
-                    return machoFile.IsValid() ? machoFile : null;
-                });
-            }
-
-            _onChangeEvent = target.Services.GetService<ISymbolService>()?.OnChangeEvent.Register(() => {
-                ServiceProvider.RemoveService(typeof(MachOModule)); 
-                ServiceProvider.RemoveService(typeof(ELFModule));
-                ServiceProvider.RemoveService(typeof(PEReader));
-            });
-         }
+            ServiceContainerFactory containerFactory = services.GetService<IServiceManager>().CreateServiceContainerFactory(ServiceScope.Module, services);
+            containerFactory.AddServiceFactory<PEFile>((services) => ModuleService.GetPEInfo(ImageBase, ImageSize, out _pdbFileInfos, ref _flags));
+            _serviceContainer = containerFactory.Build();
+            _serviceContainer.AddService<IModule>(this);
+            _serviceContainer.AddService<IExportSymbols>(this);
+        }
 
-        public void Dispose()
-        {
-            _onChangeEvent?.Dispose();
+        public virtual void Dispose()
+        { 
+            _serviceContainer.RemoveService(typeof(IModule));
+            _serviceContainer.RemoveService(typeof(IExportSymbols));
+            _serviceContainer.DisposeServices();
         }
 
         #region IModule
 
         public ITarget Target => ModuleService.Target;
 
-        public IServiceProvider Services => ServiceProvider;
+        public IServiceProvider Services => _serviceContainer;
 
-        public abstract int ModuleIndex { get; }
+        public virtual int ModuleIndex { get; protected set; }
 
-        public abstract string FileName { get; }
+        public virtual string FileName { get; protected set; }
 
-        public abstract ulong ImageBase { get; }
+        public virtual ulong ImageBase { get; protected set; }
 
-        public abstract ulong ImageSize { get; }
+        public virtual ulong ImageSize { get; protected set; }
 
-        public abstract uint? IndexFileSize { get; }
+        public virtual uint? IndexFileSize { get; protected set; }
 
-        public abstract uint? IndexTimeStamp { get; }
+        public virtual uint? IndexTimeStamp { get; protected set; }
 
         public bool IsPEImage
         {
@@ -127,11 +83,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 {
                     return true;
                 }
-                else
-                {
-                    GetPEInfo();
-                    return (_flags & Flags.IsPEImage) != 0;
-                }
+                Services.GetService<PEFile>();
+                return (_flags & Flags.IsPEImage) != 0;
             }
         }
 
@@ -139,7 +92,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             get
             {
-                GetPEInfo();
+                Services.GetService<PEFile>();
                 return (_flags & Flags.IsManaged) != 0;
             }
         }
@@ -148,7 +101,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             get
             {
-                GetPEInfo();
+                Services.GetService<PEFile>();
                 if ((_flags & Flags.IsFileLayout) != 0)
                 {
                     return true;
@@ -188,7 +141,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
         public IEnumerable<PdbFileInfo> GetPdbFileInfos()
         {
-            GetPEInfo();
+            Services.GetService<PEFile>();
             Debug.Assert(_pdbFileInfos is not null);
             return _pdbFileInfos;
         }
@@ -201,7 +154,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 {
                     try
                     {
-                        Stream stream = ModuleService.RawMemoryService.CreateMemoryStream();
+                        Stream stream = ModuleService.MemoryService.CreateMemoryStream();
                         var elfFile = new ELFFile(new StreamAddressSpace(stream), ImageBase, true);
                         if (elfFile.IsValid())
                         {
@@ -256,7 +209,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             {
                 if (ImageSize > 0)
                 {
-                    ModuleInfo module = ModuleInfo.TryCreate(Target.Services.GetService<DataReader>(), ImageBase, FileName);
+                    ModuleInfo module = ModuleInfo.TryCreate(Services.GetService<IDataReader>(), ImageBase, FileName);
                     if (module is not null)
                     {
                         address = module.GetExportSymbolAddress(name);
@@ -279,8 +232,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             Version version = null;
 
-            PEFile peFile = GetPEInfo();
-            if (peFile != null)
+            PEFile peFile = Services.GetService<PEFile>();
+            if (peFile is not null)
             {
                 try
                 {
@@ -329,15 +282,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return version;
         }
 
-        protected PEFile GetPEInfo()
+        protected string GetVersionStringInner()
         {
-            if (InitializeValue(Flags.InitializePEInfo))
+            if (ModuleService.Target.OperatingSystem != OSPlatform.Windows && !IsPEImage)
             {
-                _peFile = ModuleService.GetPEInfo(ImageBase, ImageSize, out _pdbFileInfos, ref _flags);
+                return ModuleService.GetVersionString(this);
             }
-            return _peFile;
+            return null;
         }
-
         protected bool InitializeValue(Flags flag)
         {
             if ((_flags & flag) == 0)
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs
new file mode 100644 (file)
index 0000000..d33ce7b
--- /dev/null
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.FileFormats.PE;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    /// <summary>
+    /// Create a IModule instance from a base address.
+    /// </summary>
+    public class ModuleFromAddress : Module
+    {
+        private Version _version;
+        private string _versionString;
+
+        public ModuleFromAddress(ModuleService moduleService, int moduleIndex, ulong imageBase, ulong imageSize, string imageName)
+            : base(moduleService.Services)
+        {
+            ModuleService = moduleService;
+            ModuleIndex = moduleIndex;
+            ImageBase = imageBase;
+            ImageSize = imageSize;
+            FileName = imageName;
+        }
+
+        #region IModule
+
+        public override uint? IndexTimeStamp
+        {
+            get
+            {
+                PEFile peFile = Services.GetService<PEFile>();
+                return peFile?.Timestamp;
+            }
+        }
+
+        public override uint? IndexFileSize
+        {
+            get
+            {
+                PEFile peFile = Services.GetService<PEFile>();
+                return peFile?.SizeOfImage;
+            }
+        }
+
+        public override Version GetVersionData()
+        {
+            if (InitializeValue(Module.Flags.InitializeVersion))
+            {
+                _version = GetVersionInner();
+            }
+            return _version;
+        }
+
+        public override string GetVersionString()
+        {
+            if (InitializeValue(Module.Flags.InitializeProductVersion))
+            {
+                _versionString = GetVersionStringInner();
+            }
+            return _versionString;
+        }
+
+        public override string LoadSymbols()
+        {
+            return ModuleService.SymbolService.DownloadSymbolFile(this);
+        }
+
+        #endregion
+
+        protected override ModuleService ModuleService { get; }
+    }
+}
index bd437e85e748862d8e14369eb5e1ba384a1372a7..2eb6f850aead44864b024f317b340a41bd64e942 100644 (file)
@@ -19,7 +19,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// <summary>
     /// Module service base implementation
     /// </summary>
-    public abstract class ModuleService : IModuleService
+    public abstract class ModuleService : IModuleService, IDisposable
     {
         [Flags]
         internal enum ELFProgramHeaderAttributes : uint
@@ -34,8 +34,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         // MachO writable segment attribute
         const uint VmProtWrite = 0x02;
 
-        internal protected readonly ITarget Target;
-        internal protected IMemoryService RawMemoryService;
         private IMemoryService _memoryService;
         private ISymbolService _symbolService;
         private ReadVirtualCache _versionCache;
@@ -45,26 +43,33 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         private static readonly byte[] s_versionString = Encoding.ASCII.GetBytes("@(#)Version ");
         private static readonly int s_versionLength = s_versionString.Length;
 
-        public ModuleService(ITarget target, IMemoryService rawMemoryService)
+        internal protected readonly IServiceProvider Services;
+        internal protected readonly ITarget Target;
+
+        public ModuleService(IServiceProvider services)
         {
-            Debug.Assert(target != null);
-            Target = target;
-            RawMemoryService = rawMemoryService;
+            Services = services;
+            Target = services.GetService<ITarget>();
+            Target.OnFlushEvent.Register(Flush);
+        }
+
+        public void Dispose() => Flush();
 
-            target.OnFlushEvent.Register(() => {
-                _versionCache?.Clear();
-                if (_modules != null)
+        private void Flush()
+        {
+            _versionCache?.Clear();
+            if (_modules is not null)
+            {
+                foreach (IModule module in _modules.Values)
                 {
-                    foreach (IModule module in _modules.Values)
-                    {
-                        if (module is IDisposable disposable) {
-                            disposable.Dispose();
-                        }
+                    if (module is IDisposable disposable) {
+                        disposable.Dispose();
                     }
                 }
+                _modules.Clear();
                 _modules = null;
-                _sortedByBaseAddress = null;
-            });
+            }
+            _sortedByBaseAddress = null;
         }
 
         #region IModuleService
@@ -116,7 +121,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <returns>module or null</returns>
         IModule IModuleService.GetModuleFromAddress(ulong address)
         {
-            Debug.Assert((address & ~RawMemoryService.SignExtensionMask()) == 0);
+            Debug.Assert((address & ~MemoryService.SignExtensionMask()) == 0);
             IModule[] modules = GetSortedModules();
             int min = 0, max = modules.Length - 1;
 
@@ -127,7 +132,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 IModule module = modules[mid];
 
                 ulong start = module.ImageBase;
-                Debug.Assert((start & ~RawMemoryService.SignExtensionMask()) == 0);
+                Debug.Assert((start & ~MemoryService.SignExtensionMask()) == 0);
                 ulong end = start + module.ImageSize;
 
                 if (address >= start && address < end) {
@@ -170,7 +175,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// </summary>
         private Dictionary<ulong, IModule> GetModules()
         {
-            if (_modules == null)
+            if (_modules is null)
             {
                 _modules = GetModulesInner();
             }
@@ -183,7 +188,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <returns></returns>
         private IModule[] GetSortedModules()
         {
-            if (_sortedByBaseAddress == null)
+            if (_sortedByBaseAddress is null)
             {
                 _sortedByBaseAddress = GetModules().OrderBy((pair) => pair.Key).Select((pair) => pair.Value).ToArray();
             }
@@ -260,7 +265,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             flags = 0;
             try
             {
-                Stream stream = RawMemoryService.CreateMemoryStream(address, size);
+                Stream stream = MemoryService.CreateMemoryStream(address, size);
                 PEFile peFile = new(new StreamAddressSpace(stream), isVirtual);
                 if (peFile.IsValid())
                 {
@@ -288,7 +293,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             // This code is called by the image mapping memory service so it needs to use the
             // original or raw memory service to prevent recursion so it can't use the ELFFile
             // or MachOFile instance that is available from the IModule.Services provider.
-            Stream stream = RawMemoryService.CreateMemoryStream();
+            Stream stream = MemoryService.CreateMemoryStream();
             byte[] buildId = null;
             try
             {
@@ -321,7 +326,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// </summary>
         /// <param name="module">module to get version string</param>
         /// <returns>version string or null</returns>
-        protected string GetVersionString(IModule module)
+        internal string GetVersionString(IModule module)
         {
             try
             {
@@ -389,9 +394,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             byte[] buffer = new byte[s_versionString.Length];
 
-            if (_versionCache == null)
-            {
-                // We use the possibly mapped memory service to find the version string in case it isn't in the dump.
+            // We use the mapped memory service to find the version string in case it isn't in the dump.
+            if (_versionCache is null) {
                 _versionCache = new ReadVirtualCache(MemoryService);
             }
             _versionCache.Clear();
@@ -456,9 +460,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             }
         } 
 
-        internal protected IMemoryService MemoryService => _memoryService ??= Target.Services.GetService<IMemoryService>();
+        internal protected IMemoryService MemoryService => _memoryService ??= Services.GetService<IMemoryService>();
 
-        internal protected ISymbolService SymbolService => _symbolService ??= Target.Services.GetService<ISymbolService>(); 
+        internal protected ISymbolService SymbolService => _symbolService ??= Services.GetService<ISymbolService>(); 
 
         /// <summary>
         /// Search memory helper class
index 0ad0019c47d79e85f4c581137c2462406b43572b..4b3e2dba465bb4b063f7b87031c6da7f2f6bc338 100644 (file)
@@ -29,7 +29,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             private string _versionString;
 
             public ModuleFromDataReader(ModuleServiceFromDataReader moduleService, int moduleIndex, ModuleInfo moduleInfo, ulong imageSize)
-                : base(moduleService.Target)
+                : base(moduleService.Services)
             {
                 _moduleService = moduleService;
                 _moduleInfo = moduleInfo;
@@ -39,8 +39,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
             #region IModule
 
-            public override int ModuleIndex { get; }
-
             public override string FileName => _moduleInfo.FileName;
 
             public override ulong ImageBase => _moduleInfo.ImageBase;
@@ -75,10 +73,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     }
                     else
                     {
-                        if (_moduleService.Target.OperatingSystem != OSPlatform.Windows)
-                        {
-                            _version = GetVersionInner();
-                        }
+                        _version = GetVersionInner();
                     }
                 }
                 return _version;
@@ -88,10 +83,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             {
                 if (InitializeValue(Module.Flags.InitializeProductVersion))
                 {
-                    if (_moduleService.Target.OperatingSystem != OSPlatform.Windows && !IsPEImage)
-                    {
-                        _versionString = _moduleService.GetVersionString(this);
-                    }
+                    _versionString = GetVersionStringInner();
                 }
                 return _versionString;
             }
@@ -114,8 +106,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
         private readonly IDataReader _dataReader;
 
-        public ModuleServiceFromDataReader(ITarget target, IMemoryService rawMemoryService, IDataReader dataReader)
-            : base(target, rawMemoryService)
+        public ModuleServiceFromDataReader(IServiceProvider services, IDataReader dataReader)
+            : base(services)
         {
             _dataReader = dataReader;
         }
@@ -159,14 +151,13 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     try
                     {
                         modules.Add(moduleInfo.ImageBase, module);
+                        moduleIndex++;
                     }
                     catch (ArgumentException)
                     {
                         Trace.TraceError($"GetModules(): duplicate module base '{module}' dup '{modules[moduleInfo.ImageBase]}'");
                     }
                 }
-
-                moduleIndex++;
             }
             return modules;
         }
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/PEModule.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/PEModule.cs
new file mode 100644 (file)
index 0000000..449f37d
--- /dev/null
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection.PortableExecutable;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    /// <summary>
+    /// PEModule service that provides downloaded module PEReader wrapper.
+    /// </summary>
+    public class PEModule : IDisposable
+    {
+        private readonly IModule _module;
+        private readonly ISymbolService _symbolService;
+        private readonly IDisposable _onChangeEvent;
+        private PEReader _reader;
+
+        /// <summary>
+        /// Creates a PEModule service instance of the downloaded or local (if exists) module file.
+        /// </summary>
+        [ServiceExport(Scope = ServiceScope.Module)]
+        public static PEModule CreatePEModule(IModule module, ISymbolService symbolService)
+        {
+            if (module.IndexTimeStamp.HasValue && module.IndexFileSize.HasValue)
+            {
+                return new PEModule(module, symbolService);
+            }
+            return null;
+        }
+
+        public PEReader GetPEReader()
+        {
+            if (_reader == null)
+            {
+                _reader = Utilities.OpenPEReader(_symbolService.DownloadModuleFile(_module));
+            }
+            return _reader;
+        }
+
+        private PEModule(IModule module, ISymbolService symbolService)
+        {
+            _module = module;
+            _symbolService = symbolService;
+            _onChangeEvent = symbolService.OnChangeEvent.Register(() => {
+                _reader?.Dispose();
+                _reader = null;
+            });
+        }
+
+        public void Dispose()
+        {
+            _reader?.Dispose();
+            _onChangeEvent.Dispose();
+        }
+    }
+}
\ No newline at end of file
index eb0d608f8598813d03878e8cc0869168609e4c8a..148d3c1abcbbdcb66983aa1bb601fd82a03fbce3 100644 (file)
@@ -16,23 +16,24 @@ using System.Text;
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
     /// <summary>
-    /// IRuntime instance implementation
+    /// ClrMD runtime instance implementation
     /// </summary>
-    public class Runtime : IRuntime
+    public class Runtime : IRuntime, IDisposable
     {
         private readonly ClrInfo _clrInfo;
-        private ISymbolService _symbolService;
-        private ClrRuntime _clrRuntime;
+        private readonly IDisposable _onFlushEvent;
+        private readonly ISymbolService _symbolService;
         private string _dacFilePath;
         private string _dbiFilePath;
 
-        public readonly ServiceProvider ServiceProvider;
+        protected readonly ServiceContainer _serviceContainer;
 
-        public Runtime(ITarget target, int id, ClrInfo clrInfo)
+        public Runtime(IServiceProvider services, int id, ClrInfo clrInfo)
         {
-            Target = target ?? throw new ArgumentNullException(nameof(target));
+            Target = services.GetService<ITarget>() ?? throw new ArgumentNullException();
             Id = id;
             _clrInfo = clrInfo ?? throw new ArgumentNullException(nameof(clrInfo));
+            _symbolService = services.GetService<ISymbolService>();
 
             RuntimeType = RuntimeType.Unknown;
             if (clrInfo.Flavor == ClrFlavor.Core) {
@@ -41,24 +42,41 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             else if (clrInfo.Flavor == ClrFlavor.Desktop) {
                 RuntimeType = RuntimeType.Desktop;
             }
-            RuntimeModule = target.Services.GetService<IModuleService>().GetModuleFromBaseAddress(clrInfo.ModuleInfo.ImageBase);
+            RuntimeModule = services.GetService<IModuleService>().GetModuleFromBaseAddress(clrInfo.ModuleInfo.ImageBase);
 
-            ServiceProvider = new ServiceProvider();
-            ServiceProvider.AddService<ClrInfo>(clrInfo);
-            ServiceProvider.AddServiceFactoryWithNoCaching<ClrRuntime>(() => CreateRuntime());
+            ServiceContainerFactory containerFactory = services.GetService<IServiceManager>().CreateServiceContainerFactory(ServiceScope.Runtime, services);
+            containerFactory .AddServiceFactory<ClrRuntime>((services) => CreateRuntime());
+            _serviceContainer = containerFactory.Build();
+            _serviceContainer.AddService<IRuntime>(this);
+            _serviceContainer.AddService<ClrInfo>(clrInfo);
 
-            target.OnFlushEvent.Register(() => _clrRuntime?.FlushCachedData());
+            _onFlushEvent = Target.OnFlushEvent.Register(Flush);
 
             Trace.TraceInformation($"Created runtime #{id} {clrInfo.Flavor} {clrInfo}");
         }
 
+        void IDisposable.Dispose()
+        {
+            _serviceContainer.RemoveService(typeof(IRuntime));
+            _serviceContainer.DisposeServices();
+            _onFlushEvent.Dispose();
+        }
+
+        private void Flush()
+        {
+            if (_serviceContainer.TryGetCachedService(typeof(ClrRuntime), out object service))
+            {
+                ((ClrRuntime)service).FlushCachedData();
+            }
+        }
+
         #region IRuntime
 
         public int Id { get; }
 
         public ITarget Target { get; }
 
-        public IServiceProvider Services => ServiceProvider;
+        public IServiceProvider Services => _serviceContainer;
 
         public RuntimeType RuntimeType { get; }
 
@@ -87,38 +105,35 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         #endregion
 
         /// <summary>
-        /// Create ClrRuntime helper
+        /// Create ClrRuntime instance
         /// </summary>
         private ClrRuntime CreateRuntime()
         {
-            if (_clrRuntime is null)
+            string dacFilePath = GetDacFilePath();
+            if (dacFilePath is not null)
             {
-                string dacFilePath = GetDacFilePath();
-                if (dacFilePath is not null)
+                Trace.TraceInformation($"Creating ClrRuntime #{Id} {dacFilePath}");
+                try
                 {
-                    Trace.TraceInformation($"Creating ClrRuntime #{Id} {dacFilePath}");
-                    try
-                    {
-                        // Ignore the DAC version mismatch that can happen because the clrmd ELF dump reader 
-                        // returns 0.0.0.0 for the runtime module that the DAC is matched against.
-                        _clrRuntime = _clrInfo.CreateRuntime(dacFilePath, ignoreMismatch: true);
-                    }
-                    catch (Exception ex) when
-                       (ex is DllNotFoundException || 
-                        ex is FileNotFoundException || 
-                        ex is InvalidOperationException || 
-                        ex is InvalidDataException || 
-                        ex is ClrDiagnosticsException)
-                    {
-                        Trace.TraceError("CreateRuntime FAILED: {0}", ex.ToString());
-                    }
+                    // Ignore the DAC version mismatch that can happen because the clrmd ELF dump reader 
+                    // returns 0.0.0.0 for the runtime module that the DAC is matched against.
+                    return _clrInfo.CreateRuntime(dacFilePath, ignoreMismatch: true);
                 }
-                else
+                catch (Exception ex) when
+                   (ex is DllNotFoundException || 
+                    ex is FileNotFoundException || 
+                    ex is InvalidOperationException || 
+                    ex is InvalidDataException || 
+                    ex is ClrDiagnosticsException)
                 {
-                    Trace.TraceError($"Could not find or download matching DAC for this runtime: {RuntimeModule.FileName}");
+                    Trace.TraceError("CreateRuntime FAILED: {0}", ex.ToString());
                 }
             }
-            return _clrRuntime;
+            else
+            {
+                Trace.TraceError($"Could not find or download matching DAC for this runtime: {RuntimeModule.FileName}");
+            }
+            return null;
         }
 
         private string GetLibraryPath(DebugLibraryKind kind)
@@ -173,7 +188,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             OSPlatform platform = Target.OperatingSystem;
             string filePath = null;
 
-            if (SymbolService.IsSymbolStoreEnabled)
+            if (_symbolService.IsSymbolStoreEnabled)
             {
                 SymbolStoreKey key = null;
 
@@ -234,7 +249,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 if (key is not null)
                 {
                     // Now download the DAC module from the symbol server
-                    filePath = SymbolService.DownloadFile(key);
+                    filePath = _symbolService.DownloadFile(key);
                 }
             }
             else
@@ -244,8 +259,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return filePath;
         }
 
-        private ISymbolService SymbolService => _symbolService ??= Target.Services.GetService<ISymbolService>(); 
-
         public override bool Equals(object obj)
         {
             IRuntime runtime = (IRuntime)obj;
@@ -258,10 +271,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         }
 
         private static readonly string[] s_runtimeTypeNames = {
+            "Unknown",
             "Desktop .NET Framework",
             ".NET Core",
             ".NET Core (single-file)",
-            "Unknown"
+            "Other"
         };
 
         public override string ToString()
@@ -269,21 +283,24 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             var sb = new StringBuilder();
             string config = s_runtimeTypeNames[(int)RuntimeType];
             string index = _clrInfo.BuildId.IsDefaultOrEmpty ? $"{_clrInfo.IndexTimeStamp:X8} {_clrInfo.IndexFileSize:X8}" : _clrInfo.BuildId.ToHex();
-            sb.AppendLine($"#{Id} {config} runtime at {RuntimeModule.ImageBase:X16} size {RuntimeModule.ImageSize:X8} index {index}");
+            sb.AppendLine($"#{Id} {config} runtime {_clrInfo} at {RuntimeModule.ImageBase:X16} size {RuntimeModule.ImageSize:X8} index {index}");
             if (_clrInfo.IsSingleFile) {
-                sb.AppendLine($"    Single-file runtime module path: {RuntimeModule.FileName}");
+                sb.Append($"    Single-file runtime module path: {RuntimeModule.FileName}");
             }
             else {
-                sb.AppendLine($"    Runtime module path: {RuntimeModule.FileName}");
+                sb.Append($"    Runtime module path: {RuntimeModule.FileName}");
             }
             if (RuntimeModuleDirectory is not null) {
-                sb.AppendLine($"    Runtime module directory: {RuntimeModuleDirectory}");
+                sb.AppendLine();
+                sb.Append($"    Runtime module directory: {RuntimeModuleDirectory}");
             }
             if (_dacFilePath is not null) {
-                sb.AppendLine($"    DAC: {_dacFilePath}");
+                sb.AppendLine();
+                sb.Append($"    DAC: {_dacFilePath}");
             }
             if (_dbiFilePath is not null) {
-                sb.AppendLine($"    DBI: {_dbiFilePath}");
+                sb.AppendLine();
+                sb.Append($"    DBI: {_dbiFilePath}");
             }
             return sb.ToString();
         }
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeProvider.cs
new file mode 100644 (file)
index 0000000..1482f3f
--- /dev/null
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    /// <summary>
+    /// ClrMD runtime provider implementation
+    /// </summary>
+    [ServiceExport(Type = typeof(IRuntimeProvider), Scope = ServiceScope.Provider)]
+    public class RuntimeProvider : IRuntimeProvider
+    {
+        private readonly IServiceProvider _services;
+
+        public RuntimeProvider(IServiceProvider services)
+        {
+            _services = services;
+        }
+
+        #region IRuntimeProvider
+
+        /// <summary>
+        /// Returns the list of .NET runtimes in the target
+        /// </summary>
+        /// <param name="startingRuntimeId">The starting runtime id for this provider</param>
+        public IEnumerable<IRuntime> EnumerateRuntimes(int startingRuntimeId)
+        {
+            DataTarget dataTarget = new(new CustomDataTarget(_services.GetService<IDataReader>())) {
+                FileLocator = null
+            };
+            for (int i = 0; i < dataTarget.ClrVersions.Length; i++)
+            {
+                yield return new Runtime(_services, startingRuntimeId + i, dataTarget.ClrVersions[i]);
+            }
+        }
+
+        #endregion
+    }
+}
index 74e93da88d04c9165bd5f2b735a20f56e41bb22b..43ae8e555aff28052f308af82df2e617a917036e 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using Microsoft.Diagnostics.Runtime;
 using System;
 using System.Collections.Generic;
 using System.Text;
@@ -10,36 +9,37 @@ using System.Text;
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
     /// <summary>
-    /// ClrMD runtime service implementation
+    /// Runtime service implementation
     /// </summary>
-    public class RuntimeService : IRuntimeService
+    [ServiceExport(Type = typeof(IRuntimeService), Scope = ServiceScope.Target)]
+    public class RuntimeService : IRuntimeService, IDisposable
     {
-        private readonly ITarget _target;
-        private readonly IDisposable _onFlushEvent;
-        private DataTarget _dataTarget;
-        private List<Runtime> _runtimes;
-        private IContextService _contextService;
+        private readonly IServiceProvider _services;
+        private readonly IServiceManager _serviceManager;
+        private List<IRuntime> _runtimes;
 
-        public RuntimeService(ITarget target)
+        public RuntimeService(IServiceProvider services, ITarget target)
         {
-            _target = target;
-            _onFlushEvent = target.OnFlushEvent.Register(() => {
-                if (_runtimes is not null && _runtimes.Count == 0)
+            _services = services;
+            _serviceManager = services.GetService<IServiceManager>();
+            target.OnFlushEvent.Register(Flush);
+        }
+
+        void IDisposable.Dispose() => Flush();
+
+        private void Flush()
+        {
+            if (_runtimes is not null)
+            {
+                foreach (IRuntime runtime in _runtimes)
                 {
-                    // If there are no runtimes, try find them again when the target stops
-                    _runtimes = null;
-                    _dataTarget?.Dispose();
-                    _dataTarget = null;
+                    if (runtime is IDisposable disposable) {
+                        disposable.Dispose();
+                    }
                 }
-            });
-            // Can't make RuntimeService IDisposable directly because _dataTarget.Dispose() disposes the IDataReader 
-            // passed which is this RuntimeService instance which would call _dataTarget.Dispose again and causing a 
-            // stack overflow.
-            target.OnDestroyEvent.Register(() => {
-                _dataTarget?.Dispose();
-                _dataTarget = null;
-                _onFlushEvent.Dispose();
-            });
+                _runtimes.Clear();
+                _runtimes = null;
+            }
         }
 
         #region IRuntimeService
@@ -51,38 +51,27 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             if (_runtimes is null)
             {
-                _runtimes = new List<Runtime>();
-                if (_dataTarget is null)
-                {
-                    _dataTarget = new DataTarget(new CustomDataTarget(_target.Services.GetService<DataReader>())) {
-                        FileLocator = null
-                    };
-                }
-                if (_dataTarget is not null)
+                _runtimes = new List<IRuntime>();
+                foreach (ServiceFactory factory in _serviceManager.EnumerateProviderFactories(typeof(IRuntimeProvider)))
                 {
-                    for (int i = 0; i < _dataTarget.ClrVersions.Length; i++)
-                    {
-                        _runtimes.Add(new Runtime(_target, i, _dataTarget.ClrVersions[i]));
-                    }
+                    IRuntimeProvider provider = (IRuntimeProvider)factory(_services);
+                    _runtimes.AddRange(provider.EnumerateRuntimes(_runtimes.Count));
                 }
             }
             return _runtimes;
         }
 
         #endregion
-
-        private IRuntime CurrentRuntime => ContextService.Services.GetService<IRuntime>();
-
-        private IContextService ContextService => _contextService ??= _target.Services.GetService<IContextService>();
-
+    
         public override string ToString()
         {
             var sb = new StringBuilder();
             if (_runtimes is not null)
             {
+                IRuntime currentRuntime = _services.GetService<IContextService>()?.GetCurrentRuntime();
                 foreach (IRuntime runtime in _runtimes)
                 {
-                    string current = _runtimes.Count > 1 ? runtime == CurrentRuntime ? "*" : " " : "";
+                    string current = _runtimes.Count > 1 ? runtime == currentRuntime ? "*" : " " : "";
                     sb.Append(current);
                     sb.AppendLine(runtime.ToString());
                 }
index 79e9e88f23ecd43258870c0f0dafbfc5489e4aff..b49b1fa1900e9354423450d76e6274067fd8f084 100644 (file)
@@ -41,7 +41,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             }
         }
 
-        private readonly LinkedListNode _events = new LinkedListNode();
+        private readonly LinkedListNode _events = new();
 
         public ServiceEvent()
         {
@@ -67,4 +67,66 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             }
         }
     }
+
+    /// <summary>
+    /// The service event with one parameter implementation
+    /// </summary>
+    public class ServiceEvent<T> : IServiceEvent<T>
+    {
+        private class EventNode : LinkedListNode, IDisposable
+        {
+            private readonly Action<T> _callback;
+
+            internal EventNode(bool oneshot, Action<T> callback)
+            {
+                if (oneshot)
+                {
+                    _callback = (T parameter) => {
+                        callback(parameter);
+                        Remove();
+                    };
+                }
+                else
+                {
+                    _callback = callback;
+                }
+            }
+
+            internal void Fire(T parameter)
+            {
+                _callback(parameter);
+            }
+
+            void IDisposable.Dispose()
+            {
+                Remove();
+            }
+        }
+
+        private readonly LinkedListNode _events = new();
+
+        public ServiceEvent()
+        {
+        }
+
+        public IDisposable Register(Action<T> callback) => Register(oneshot: false, callback);
+
+        public IDisposable RegisterOneShot(Action<T> callback) => Register(oneshot: true, callback);
+
+        private IDisposable Register(bool oneshot, Action<T> callback)
+        {
+            // Insert at the end of the list
+            var node = new EventNode(oneshot, callback);
+            _events.InsertBefore(node);
+            return node;
+        }
+
+        public void Fire(T parameter)
+        {
+            foreach (EventNode node in _events.GetValues<EventNode>())
+            {
+                node.Fire(parameter);
+            }
+        }
+    }
 }
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceManager.cs
new file mode 100644 (file)
index 0000000..2d3abf5
--- /dev/null
@@ -0,0 +1,234 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.IO;
+using System.Diagnostics;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    /// <summary>
+    /// The service manager registers any ServiceExportAttribute on types and methods and sets properties,
+    /// fields and methods marked  with the ServiceImportAttribute that match the provided services. Tracks 
+    /// any unresolved service requests and injects them when the service is registered.
+    /// </summary>
+    public class ServiceManager : IServiceManager
+    {
+        private readonly Dictionary<Type, ServiceFactory>[] _factories;
+        private readonly Dictionary<Type, List<ServiceFactory>> _providerFactories;
+        private readonly ServiceEvent<Assembly> _notifyExtensionLoad;
+        private bool _finalized;
+
+        /// <summary>
+        /// This event fires when an extension assembly is loaded
+        /// </summary>
+        public IServiceEvent<Assembly> NotifyExtensionLoad => _notifyExtensionLoad;
+
+        /// <summary>
+        /// Create a service manager instance
+        /// </summary>
+        public ServiceManager()
+        {
+            _factories = new Dictionary<Type, ServiceFactory>[(int)ServiceScope.Max];
+            _providerFactories = new Dictionary<Type, List<ServiceFactory>>();
+            _notifyExtensionLoad = new ServiceEvent<Assembly>();
+            for (int i = 0; i < (int)ServiceScope.Max; i++)
+            {
+                _factories[i] = new Dictionary<Type, ServiceFactory>();
+            }
+        }
+
+        /// <summary>
+        /// Creates a new service container factory with all the registered factories for the given scope.
+        /// </summary>
+        /// <param name="scope">global, per-target, per-runtime, etc. service type</param>
+        /// <param name="parent">parent service provider to chain</param>
+        /// <returns></returns>
+        public ServiceContainerFactory CreateServiceContainerFactory(ServiceScope scope, IServiceProvider parent)
+        {
+            if (!_finalized) throw new InvalidOperationException();
+            return new ServiceContainerFactory(parent, _factories[(int)scope]);
+        }
+
+        /// <summary>
+        /// Get the provider factories for a type or interface.
+        /// </summary>
+        /// <param name="providerType">type or interface</param>
+        /// <returns>the provider factories for the type</returns>
+        public IEnumerable<ServiceFactory> EnumerateProviderFactories(Type providerType)
+        {
+            if (!_finalized) throw new InvalidOperationException();
+
+            if (_providerFactories.TryGetValue(providerType, out List<ServiceFactory> factories))
+            {
+                return factories;
+            }
+            return Array.Empty<ServiceFactory>();
+        }
+
+        /// <summary>
+        /// Finds all the ServiceExport attributes in the assembly and registers.
+        /// </summary>
+        /// <param name="assembly">service implementation assembly</param>
+        public void RegisterExportedServices(Assembly assembly)
+        {
+            foreach (Type serviceType in assembly.GetExportedTypes())
+            {
+                if (serviceType.IsClass)
+                {
+                    RegisterExportedServices(serviceType);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Finds all the ServiceExport attributes in the type and registers.
+        /// </summary>
+        /// <param name="serviceType">service implementation type</param>
+        public void RegisterExportedServices(Type serviceType)
+        {
+            if (_finalized) throw new InvalidOperationException();
+
+            for (Type currentType = serviceType; currentType is not null; currentType = currentType.BaseType)
+            {
+                if (currentType == typeof(object) || currentType == typeof(ValueType))
+                {
+                    break;
+                }
+                ServiceExportAttribute serviceAttribute = currentType.GetCustomAttribute<ServiceExportAttribute>(inherit: false);
+                if (serviceAttribute is not null)
+                {
+                    ServiceFactory factory = (provider) => Utilities.CreateInstance(serviceType, provider);
+                    AddServiceFactory(serviceAttribute.Scope, serviceAttribute.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<ServiceExportAttribute>(inherit: false);
+                    if (serviceAttribute is not null)
+                    {
+                        AddServiceFactory(serviceAttribute.Scope, serviceAttribute.Type ?? methodInfo.ReturnType, (provider) => Utilities.CreateInstance(methodInfo, provider));
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Add service containerFactory for the specific scope.
+        /// </summary>
+        /// <typeparam name="T">service type</typeparam>
+        /// <param name="scope">global, per-target, per-runtime, etc. service type</param>
+        /// <param name="factory">function to create service instance</param>
+        public void AddServiceFactory<T>(ServiceScope scope, ServiceFactory factory) => AddServiceFactory(scope, typeof(T), factory);
+
+        /// <summary>
+        /// Add service containerFactory for the specific scope.
+        /// </summary>
+        /// <param name="scope">global, per-target, per-runtime, etc. service type</param>
+        /// <param name="serviceType">service type or interface</param>
+        /// <param name="factory">function to create service instance</param>
+        public void AddServiceFactory(ServiceScope scope, Type serviceType, ServiceFactory factory)
+        {
+            if (factory is null) throw new ArgumentNullException(nameof(factory));
+            if (_finalized) throw new InvalidOperationException();
+
+            if (scope == ServiceScope.Provider)
+            {
+                if (!_providerFactories.TryGetValue(serviceType, out List<ServiceFactory> factories))
+                {
+                    _providerFactories.Add(serviceType, factories = new List<ServiceFactory>());
+                }
+                factories.Add(factory);
+            }
+            else
+            {
+                _factories[(int)scope].Add(serviceType, factory);
+            }
+        }
+
+        /// <summary>
+        /// Load any extra extensions in the search path
+        /// </summary>
+        public void LoadExtensions()
+        {
+            if (_finalized) throw new InvalidOperationException();
+
+            List<string> extensionPaths = new();
+            string diagnosticExtensions = Environment.GetEnvironmentVariable("DOTNET_DIAGNOSTIC_EXTENSIONS");
+            if (!string.IsNullOrEmpty(diagnosticExtensions))
+            {
+                string[] paths = diagnosticExtensions.Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
+                extensionPaths.AddRange(paths);
+            }
+            string assemblyPath = Assembly.GetExecutingAssembly().Location;
+            if (!string.IsNullOrEmpty(assemblyPath))
+            {
+                string searchPath = Path.Combine(Path.GetDirectoryName(assemblyPath), "extensions");
+                if (Directory.Exists(searchPath))
+                {
+                    try
+                    {
+                        string[] extensionFiles = Directory.GetFiles(searchPath, "*.dll");
+                        extensionPaths.AddRange(extensionFiles);
+                    }
+                    catch (Exception ex) when (ex is IOException || ex is ArgumentException || ex is UnauthorizedAccessException || ex is System.Security.SecurityException)
+                    {
+                        Trace.TraceError(ex.ToString());
+                    }
+                }
+            }
+            foreach (string extensionPath in extensionPaths)
+            {
+                LoadExtension(extensionPath);
+            }
+        }
+
+        /// <summary>
+        /// Load extension from the path
+        /// </summary>
+        /// <param name="extensionPath">extension assembly path</param>
+        public void LoadExtension(string extensionPath)
+        {
+            if (_finalized) throw new InvalidOperationException();
+            Assembly assembly = null;
+            try
+            {
+                assembly = Assembly.LoadFrom(extensionPath);
+            }
+            catch (Exception ex) when (ex is IOException || ex is ArgumentException || ex is BadImageFormatException || ex is System.Security.SecurityException)
+            {
+                Trace.TraceError(ex.ToString());
+            }
+            if (assembly is not null)
+            {
+                RegisterAssembly(assembly);
+            }
+        }
+
+        /// <summary>
+        /// Register the exported services in the assembly and notify the assembly has loaded.
+        /// </summary>
+        /// <param name="assembly">extension assembly</param>
+        public void RegisterAssembly(Assembly assembly)
+        {
+            if (_finalized) throw new InvalidOperationException();
+            try
+            {
+                RegisterExportedServices(assembly);
+                _notifyExtensionLoad.Fire(assembly);
+            }
+            catch (Exception ex) when (ex is DiagnosticsException || ex is NotSupportedException || ex is FileNotFoundException)
+            {
+                Trace.TraceError(ex.ToString());
+            }
+        }
+
+        /// <summary>
+        /// Finalizes the service manager. Loading extensions or adding service factories are not allowed after this call.
+        /// </summary>
+        public void FinalizeServices() => _finalized = true;
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs
deleted file mode 100644 (file)
index 24b9896..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-
-namespace Microsoft.Diagnostics.DebugServices.Implementation
-{
-    public class ServiceProvider : IServiceProvider
-    {
-        private readonly Func<IServiceProvider>[] _parents;
-        private readonly Dictionary<Type, Func<object>> _factories;
-        private readonly Dictionary<Type, object> _services;
-
-        /// <summary>
-        /// Create a service provider instance
-        /// </summary>
-        public ServiceProvider()
-            : this(Array.Empty<Func<IServiceProvider>>())
-        {
-        }
-
-        /// <summary>
-        /// Create a service provider with parent provider
-        /// </summary>
-        /// <param name="parent">search this provider if service isn't found in this instance</param>
-        public ServiceProvider(IServiceProvider parent)
-            : this(new Func<IServiceProvider>[] { () => parent })
-        {
-        }
-
-        /// <summary>
-        /// Create a service provider with parent provider and service factories
-        /// </summary>
-        /// <param name="parents">an array of functions to return the next provider to search if service isn't found in this instance</param>
-        public ServiceProvider(Func<IServiceProvider>[] parents) 
-        {
-            _parents = parents;
-            _factories = new Dictionary<Type, Func<object>>();
-            _services = new Dictionary<Type, object>();
-        }
-
-        /// <summary>
-        /// Add service factory and cache result when requested.
-        /// </summary>
-        /// <typeparam name="T">service type</typeparam>
-        /// <param name="factory">function to create service instance</param>
-        public void AddServiceFactory<T>(Func<object> factory)
-        {
-            _factories.Add(typeof(T), () => {
-                object service = factory();
-                _services.Add(typeof(T), service);
-                return service;
-            });
-        }
-
-        /// <summary>
-        /// Add service factory. Lets the service decide on how the cache the result.
-        /// </summary>
-        /// <typeparam name="T">service type</typeparam>
-        /// <param name="factory">function to create service instance</param>
-        public void AddServiceFactoryWithNoCaching<T>(Func<object> factory) => _factories.Add(typeof(T), factory);
-
-        /// <summary>
-        /// Adds a service or context to inject into an command.
-        /// </summary>
-        /// <typeparam name="T">type of service</typeparam>
-        /// <param name="instance">service instance</param>
-        public void AddService<T>(T instance) => AddService(typeof(T), instance);
-
-        /// <summary>
-        /// Add a service instance.
-        /// </summary>
-        /// <param name="type">service type</param>
-        /// <param name="service">instance</param>
-        public void AddService(Type type, object service) => _services.Add(type, service);
-
-        /// <summary>
-        /// Flushes the cached service instance for the specified type. Does not remove the service factory registered for the type.
-        /// </summary>
-        /// <param name="type">service type</param>
-        public void RemoveService(Type type) => _services.Remove(type);
-
-        /// <summary>
-        /// Flushes all the cached instances of the services. Does not remove any of the service factories registered.
-        /// </summary>
-        public void FlushServices() => _services.Clear();
-
-        /// <summary>
-        /// Returns the instance of the service or returns null if service doesn't exist
-        /// </summary>
-        /// <param name="type">service type</param>
-        /// <returns>service instance or null</returns>
-        public object GetService(Type type)
-        {
-            if (!_services.TryGetValue(type, out object service))
-            {
-                if (_factories.TryGetValue(type, out Func<object> factory))
-                {
-                    service = factory();
-                }
-            }
-            if (service == null)
-            {
-                foreach (Func<IServiceProvider> parent in _parents)
-                {
-                    service = parent()?.GetService(type);
-                    if (service != null)
-                    {
-                        break;
-                    }
-                }
-            }
-            return service;
-        }
-    }
-}
index 58b453f1bd34a47d12ae84863b4d471b73da6a91..e5b7afbb88b7ca6ae20da95e0c476259d95f5670 100644 (file)
@@ -3,6 +3,8 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.FileFormats;
+using Microsoft.FileFormats.ELF;
+using Microsoft.FileFormats.MachO;
 using Microsoft.FileFormats.PE;
 using Microsoft.SymbolStore;
 using Microsoft.SymbolStore.KeyGenerators;
@@ -715,10 +717,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             string fileName = fileKey.FullPathName;
             if (File.Exists(fileName))
             {
-                using ELFModule elfModule = ELFModule.OpenFile(fileName);
-                if (elfModule is not null)
+                using ELFFile elfFile = Utilities.OpenELFFile(fileName);
+                if (elfFile is not null)
                 {
-                    var generator = new ELFFileKeyGenerator(Tracer.Instance, elfModule, fileName);
+                    var generator = new ELFFileKeyGenerator(Tracer.Instance, elfFile, fileName);
                     foreach (SymbolStoreKey key in generator.GetKeys(flags))
                     {
                         if (fileKey.Equals(key))
@@ -771,10 +773,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             string fileName = fileKey.FullPathName;
             if (File.Exists(fileName))
             {
-                using MachOModule machOModule = MachOModule.OpenFile(fileName);
-                if (machOModule is not null)
+                using MachOFile machOFile = Utilities.OpenMachOFile(fileName);
+                if (machOFile is not null)
                 {
-                    var generator = new MachOFileKeyGenerator(Tracer.Instance, machOModule, fileName);
+                    var generator = new MachOFileKeyGenerator(Tracer.Instance, machOFile, fileName);
                     IEnumerable<SymbolStoreKey> keys = generator.GetKeys(flags);
                     foreach (SymbolStoreKey key in keys)
                     {
index e4d66bcd1be88d9d68290a56e8bed3c854fb329e..e5f8116b608747dd54e9c17108afc4650fd10a0f 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Runtime.InteropServices;
@@ -15,12 +14,13 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// <summary>
     /// ITarget base implementation
     /// </summary>
-    public abstract class Target : ITarget, IDisposable
+    public abstract class Target : ITarget
     {
         private readonly string _dumpPath;
         private string _tempDirectory;
+        private ServiceContainer _serviceContainer;
 
-        public readonly ServiceProvider ServiceProvider;
+        protected readonly ServiceContainerFactory _serviceContainerFactory;
 
         public Target(IHost host, int id, string dumpPath)
         {
@@ -32,13 +32,16 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             OnFlushEvent = new ServiceEvent();
             OnDestroyEvent = new ServiceEvent();
 
-            // Initialize the per-target services
-            ServiceProvider = new ServiceProvider(host.Services);
+            // Initialize the per-target services.
+            _serviceContainerFactory = host.Services.GetService<IServiceManager>().CreateServiceContainerFactory(ServiceScope.Target, host.Services);
+            _serviceContainerFactory.AddServiceFactory<ITarget>((_) => this);
+        }
 
-            // Add the per-target services
-            ServiceProvider.AddService<ITarget>(this);
-            ServiceProvider.AddServiceFactory<DataReader>(() => new DataReader(this));
-            ServiceProvider.AddServiceFactory<IRuntimeService>(() => new RuntimeService(this));
+        protected void Finished()
+        {
+            // Now the that the target is completely initialized, finalize container and fire event
+            _serviceContainer = _serviceContainerFactory.Build();
+            Host.OnTargetCreate.Fire(this);
         }
 
         #region ITarget
@@ -93,7 +96,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <summary>
         /// The per target services.
         /// </summary>
-        public IServiceProvider Services => ServiceProvider;
+        public IServiceProvider Services => _serviceContainer;
 
         /// <summary>
         /// Invoked when this target is flushed (via the Flush() call).
@@ -110,22 +113,24 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         }
 
         /// <summary>
-        /// Invoked when the target is closed.
+        /// Invoked when the target is destroyed
         /// </summary>
         public IServiceEvent OnDestroyEvent { get; }
 
-        #endregion
-
         /// <summary>
-        /// Releases the target and the target's resources.
+        /// Cleans up the target and releases target's resources.
         /// </summary>
-        public void Dispose()
+        public void Destroy()
         {
-            Trace.TraceInformation($"Disposing target #{Id}");
+            Trace.TraceInformation($"Destroy target #{Id}");
             OnDestroyEvent.Fire();
+            _serviceContainer.RemoveService(typeof(ITarget));
+            _serviceContainer.DisposeServices();
             CleanupTempDirectory();
         }
 
+        #endregion
+
         private void CleanupTempDirectory()
         {
             if (_tempDirectory != null)
@@ -166,7 +171,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             if (_dumpPath != null) {
                 sb.AppendLine($"Dump path: {_dumpPath}");
             }
-            var runtimeService = ServiceProvider.GetService<IRuntimeService>();
+            var runtimeService = Services.GetService<IRuntimeService>();
             if (runtimeService != null)
             {
                 sb.AppendLine(runtimeService.ToString());
index 07d901bb04a2ed8d9f6b404481dc8f2cc00afd08..6933cf713220be7d5f01deb122067fcbe16e0447 100644 (file)
@@ -33,32 +33,43 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             IsDump = true;
             Architecture = dataReader.Architecture;
 
-            if (dataReader.ProcessId != -1) {
+            if (dataReader.ProcessId != -1)
+            {
                 ProcessId = (uint)dataReader.ProcessId;
             }
 
             OnFlushEvent.Register(dataReader.FlushCachedData);
 
             // Add the thread, memory, and module services
-            IMemoryService rawMemoryService = new MemoryServiceFromDataReader(_dataReader);
-            ServiceProvider.AddServiceFactory<IThreadService>(() => new ThreadServiceFromDataReader(this, _dataReader));
-            ServiceProvider.AddServiceFactory<IModuleService>(() => new ModuleServiceFromDataReader(this, rawMemoryService, _dataReader));
-            ServiceProvider.AddServiceFactory<IMemoryService>(() => {
-                IMemoryService memoryService = rawMemoryService;
+            _serviceContainerFactory.AddServiceFactory<IThreadService>((services) => new ThreadServiceFromDataReader(services, _dataReader));
+            _serviceContainerFactory.AddServiceFactory<IModuleService>((services) => new ModuleServiceFromDataReader(services, _dataReader));
+            _serviceContainerFactory.AddServiceFactory<IMemoryService>((_) => {
+                IMemoryService memoryService = new MemoryServiceFromDataReader(_dataReader);
                 if (IsDump)
                 {
-                    memoryService = new ImageMappingMemoryService(this, memoryService);
-                    // Any dump created for a MacOS target does not have managed assemblies in the module service so
-                    // we need to use the metadata mapping memory service to make sure the metadata is available and
-                    // 7.0 Linux builds have an extra System.Private.CoreLib module mapping that causes the image
-                    // mapper not to be able to map in the metadata.
+                    // The target container factory needs to be cloned for the memory services so the original IMemoryService
+                    // factory can be removed so it doesn't inadvertently get called. The image mapping service is going to
+                    // replace it with the memory service instance passed. The clone.Build() creates a new separate service
+                    // container instance for the image mapping service.
+                    ServiceContainerFactory clone = _serviceContainerFactory.Clone();
+                    clone.RemoveServiceFactory<IMemoryService>();
+
+                    // The underlying host (dotnet-dump usually) doesn't map native modules into the address space
+                    memoryService = new ImageMappingMemoryService(clone.Build(), memoryService, managed: false);
+
+                    // Any dump created for a MacOS target does not have managed assemblies in the native module service so
+                    // we need to use this managed mapping memory service to make sure the metadata is available and 7.0 Linux
+                    // builds have an extra System.Private.CoreLib module mapping that causes the native image mapper not to
+                    // be able to map in the metadata.
                     if (targetOS == OSPlatform.OSX || targetOS == OSPlatform.Linux)
                     {
-                        memoryService = new MetadataMappingMemoryService(this, memoryService);
+                        memoryService = new ImageMappingMemoryService(clone.Build(), memoryService, managed: true);
                     }
                 }
                 return memoryService;
             });
+
+            Finished();
         }
     }
 }
index 577e1905ac77bf6ca0b2b485db3cc4aec9bf8874..99a972c1b2cc50dc23bd759d289f3ae419687694 100644 (file)
@@ -8,20 +8,27 @@ using System.Runtime.InteropServices;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
-    public class Thread : IThread
+    public class Thread : IThread, IDisposable
     {
         private readonly ThreadService _threadService;
         private byte[] _threadContext;
         private ulong? _teb;
 
-        public readonly ServiceProvider ServiceProvider;
+        protected readonly ServiceContainer _serviceContainer;
 
         public Thread(ThreadService threadService, int index, uint id)
         {
             _threadService = threadService;
             ThreadIndex = index;
             ThreadId = id;
-            ServiceProvider = new ServiceProvider();
+            _serviceContainer = threadService.Services.GetService<IServiceManager>().CreateServiceContainer(ServiceScope.Thread, threadService.Services);
+            _serviceContainer.AddService<IThread>(this);
+        }
+
+        void IDisposable.Dispose()
+        {
+            _serviceContainer.RemoveService(typeof(IThread));
+            _serviceContainer.DisposeServices();
         }
 
         #region IThread
@@ -32,7 +39,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
         public ITarget Target => _threadService.Target;
 
-        public IServiceProvider Services => ServiceProvider;
+        public IServiceProvider Services => _serviceContainer;
 
         public bool TryGetRegisterValue(int index, out ulong value)
         {
index e201665a533edb227c7d974d746f91858382cd1d..89cd9cee46fe1572f8ee2f9221acf820ba6d4902 100644 (file)
@@ -15,30 +15,29 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
     /// <summary>
     /// Provides thread and register info and values for the clrmd IDataReader
     /// </summary>
-    public abstract class ThreadService : IThreadService
+    public abstract class ThreadService : IThreadService, IDisposable
     {
-        internal protected readonly ITarget Target;
         private readonly int _contextSize;
         private readonly uint _contextFlags;
         private readonly Dictionary<string, RegisterInfo> _lookupByName;
         private readonly Dictionary<int, RegisterInfo> _lookupByIndex;
         private Dictionary<uint, IThread> _threads;
 
-        public ThreadService(ITarget target)
-        {
-            Target = target;
+        internal protected readonly IServiceProvider Services;
+        internal protected readonly ITarget Target;
 
-            target.OnFlushEvent.Register(() => {
-                _threads?.Clear();
-                _threads = null;
-            });
+        public ThreadService(IServiceProvider services)
+        {
+            Services = services;
+            Target = services.GetService<ITarget>();
+            Target.OnFlushEvent.Register(Flush);
 
             Type contextType;
-            switch (target.Architecture)
+            switch (Target.Architecture)
             {
                 case Architecture.X64:
                     // Dumps generated with newer dbgeng have bigger context buffers and clrmd requires the context size to at least be that size.
-                    _contextSize = target.OperatingSystem == OSPlatform.Windows ? 0x700 : AMD64Context.Size;
+                    _contextSize = Target.OperatingSystem == OSPlatform.Windows ? 0x700 : AMD64Context.Size;
                     _contextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments | AMD64Context.ContextFloatingPoint;
                     contextType = typeof(AMD64Context);
                     break;
@@ -62,7 +61,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                     break;
 
                 default:
-                    throw new PlatformNotSupportedException($"Unsupported architecture: {target.Architecture}");
+                    throw new PlatformNotSupportedException($"Unsupported architecture: {Target.Architecture}");
             }
 
             var registers = new List<RegisterInfo>();
@@ -71,7 +70,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             FieldInfo[] fields = contextType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
             foreach (FieldInfo field in fields) {
                 RegisterAttribute registerAttribute = field.GetCustomAttributes<RegisterAttribute>(inherit: false).SingleOrDefault();
-                if (registerAttribute == null) {
+                if (registerAttribute is null) {
                     continue;
                 }
                 RegisterType registerType = registerAttribute.RegisterType & RegisterType.TypeMask;
@@ -104,6 +103,23 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             Registers = registers;
         }
 
+        void IDisposable.Dispose() => Flush();
+
+        private void Flush()
+        {
+            if (_threads is not null)
+            {
+                foreach (IThread thread in _threads.Values)
+                {
+                    if (thread is IDisposable disposable) {
+                        disposable.Dispose();
+                    }
+                }
+                _threads.Clear();
+                _threads = null;
+            }
+        }
+
         #region IThreadService
 
         /// <summary>
@@ -226,7 +242,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// </summary>
         private Dictionary<uint, IThread> GetThreads()
         {
-            if (_threads == null) {
+            if (_threads is null) {
                 _threads = GetThreadsInner().OrderBy((thread) => thread.ThreadId).ToDictionary((thread) => thread.ThreadId);
             }
             return _threads;
index 68e57af5b9647e896c88a766f9c007f6f43b4931..fb5c7f35ea4a1e76ca979d160a678b07dad344fe 100644 (file)
@@ -6,7 +6,6 @@ using Microsoft.Diagnostics.Runtime;
 using Microsoft.Diagnostics.Runtime.DataReaders.Implementation;
 using System;
 using System.Collections.Generic;
-using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Linq;
 
@@ -20,8 +19,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         private readonly IDataReader _dataReader;
         private readonly IThreadReader _threadReader;
 
-        public ThreadServiceFromDataReader(ITarget target, IDataReader dataReader)
-            : base(target)
+        public ThreadServiceFromDataReader(IServiceProvider services, IDataReader dataReader)
+            : base(services)
         {
             _dataReader = dataReader;
             _threadReader = (IThreadReader)dataReader;
index 6eba2ce406efc4de866a47cbb93fa42ba0bba50f..3bbf9fea5338b4bd1221add6f745e723dbfce583 100644 (file)
@@ -2,6 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using Microsoft.FileFormats;
+using Microsoft.FileFormats.ELF;
+using Microsoft.FileFormats.MachO;
 using Microsoft.FileFormats.PE;
 using System;
 using System.Collections.Immutable;
@@ -10,6 +13,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Reflection.PortableExecutable;
+using System.Runtime.InteropServices;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
@@ -87,6 +91,98 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return null;
         }
 
+        /// <summary>
+        /// Opens and returns an ELFFile instance from the local file path
+        /// </summary>
+        /// <param name="filePath">ELF file to open</param>
+        /// <returns>ELFFile instance or null</returns>
+        public static ELFFile OpenELFFile(string filePath)
+        {
+            Stream stream = TryOpenFile(filePath);
+            if (stream is not null)
+            {
+                try
+                {
+                    ELFFile elfFile = new(new StreamAddressSpace(stream), position: 0, isDataSourceVirtualAddressSpace: false);
+                    if (!elfFile.IsValid())
+                    {
+                        Trace.TraceError($"OpenFile: not a valid file {filePath}");
+                        return null;
+                    }
+                    return elfFile;
+                }
+                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+                {
+                    Trace.TraceError($"OpenFile: {filePath} exception {ex.Message}");
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Opens and returns an MachOFile instance from the local file path
+        /// </summary>
+        /// <param name="filePath">MachO file to open</param>
+        /// <returns>MachOFile instance or null</returns>
+        public static MachOFile OpenMachOFile(string filePath)
+        {
+            Stream stream = TryOpenFile(filePath);
+            if (stream is not null)
+            {
+                try
+                {
+                    MachOFile machoModule = new(new StreamAddressSpace(stream), position: 0, dataSourceIsVirtualAddressSpace: false);
+                    if (!machoModule.IsValid())
+                    {
+                        Trace.TraceError($"OpenMachOFile: not a valid file {filePath}");
+                        return null;
+                    }
+                    return machoModule;
+                }
+                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+                {
+                    Trace.TraceError($"OpenMachOFile: {filePath} exception {ex.Message}");
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Creates a ELFFile service instance of the module in memory.
+        /// </summary>
+        [ServiceExport(Scope = ServiceScope.Module)]
+        public static ELFFile CreateELFFile(IMemoryService memoryService, IModule module)
+        {
+            if (module.Target.OperatingSystem == OSPlatform.Linux)
+            {
+                Stream stream = memoryService.CreateMemoryStream();
+                var elfFile = new ELFFile(new StreamAddressSpace(stream), module.ImageBase, true);
+                if (elfFile.IsValid())
+                {
+                    return elfFile;
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Creates a MachOFile service instance of the module in memory.
+        /// </summary>
+        [ServiceExport(Scope = ServiceScope.Module)]
+        public static MachOFile CreateMachOFile(IMemoryService memoryService, IModule module)
+        {
+            if (module.Target.OperatingSystem == OSPlatform.OSX)
+            {
+                Stream stream = memoryService.CreateMemoryStream();
+                var elfFile = new MachOFile(new StreamAddressSpace(stream), module.ImageBase, true);
+                if (elfFile.IsValid())
+                {
+                    return elfFile;
+                }
+            }
+            return null;
+        }
+
         /// <summary>
         /// Attempt to open a file stream.
         /// </summary>
@@ -109,18 +205,119 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return null;
         }
 
+        /// <summary>
+        /// Returns the .NET user directory
+        /// </summary>
+        public static string GetDotNetHomeDirectory()
+        {
+            string dotnetHome;
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+                dotnetHome = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE") ?? throw new ArgumentNullException("USERPROFILE environment variable not found"), ".dotnet");
+            }
+            else { 
+                dotnetHome = Path.Combine(Environment.GetEnvironmentVariable("HOME") ?? throw new ArgumentNullException("HOME environment variable not found"), ".dotnet");
+            }
+            return dotnetHome;
+        }
+
+        /// <summary>
+        /// Create the type instance and fill in any service imports
+        /// </summary>
+        /// <param name="type">type to create</param>
+        /// <param name="provider">service provider</param>
+        /// <returns>new instance</returns>
+        public static object CreateInstance(Type type, IServiceProvider provider)
+        {
+            object instance = InvokeConstructor(type, provider);
+            if (instance is not null)
+            {
+                ImportServices(instance, provider);
+            }
+            return instance;
+        }
+
+        /// <summary>
+        /// Call the static method (constructor) to create the instance and fill in any service imports
+        /// </summary>
+        /// <param name="method">static method (constructor) to use to create instance</param>
+        /// <param name="provider">service provider</param>
+        /// <returns>new instance</returns>
+        public static object CreateInstance(MethodBase method, IServiceProvider provider)
+        {
+            object instance = Invoke(method, null, provider);
+            if (instance is not null)
+            {
+                ImportServices(instance, provider);
+            }
+            return instance;
+        }
+
+        /// <summary>
+        /// Set any fields, property or method marked with the ServiceImportAttribute to the service requested.
+        /// </summary>
+        /// <param name="instance">object instance to process</param>
+        /// <param name="provider">service provider</param>
+        public static void ImportServices(object instance, IServiceProvider provider)
+        {
+            if (instance == null) throw new ArgumentNullException(nameof(instance));
+
+            for (Type currentType = instance.GetType(); currentType is not null; currentType = currentType.BaseType)
+            {
+                if (currentType == typeof(object) || currentType == typeof(ValueType))
+                {
+                    break;
+                }
+                FieldInfo[] fields = currentType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
+                foreach (FieldInfo field in fields)
+                {
+                    ServiceImportAttribute attribute = field.GetCustomAttribute<ServiceImportAttribute>(inherit: false);
+                    if (attribute is not null)
+                    {
+                        object serviceInstance = provider.GetService(field.FieldType);
+                        if (serviceInstance is null && !attribute.Optional)
+                        {
+                            throw new DiagnosticsException($"The {field.FieldType.Name} service is required by the {field.Name} field");
+                        }
+                        field.SetValue(instance, serviceInstance);
+                    }
+                }
+                PropertyInfo[] properties = currentType.GetProperties(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
+                foreach (PropertyInfo property in properties)
+                {
+                    ServiceImportAttribute attribute = property.GetCustomAttribute<ServiceImportAttribute>(inherit: false);
+                    if (attribute is not null)
+                    {
+                        object serviceInstance = provider.GetService(property.PropertyType);
+                        if (serviceInstance is null && !attribute.Optional)
+                        {
+                            throw new DiagnosticsException($"The {property.PropertyType.Name} service is required by the {property.Name} property");
+                        }
+                        property.SetValue(instance, serviceInstance);
+                    }
+                }
+                MethodInfo[] methods = currentType.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
+                foreach (MethodInfo method in methods)
+                {
+                    ServiceImportAttribute attribute = method.GetCustomAttribute<ServiceImportAttribute>(inherit: false);
+                    if (attribute is not null)
+                    {
+                        Utilities.Invoke(method, instance, provider);
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// Call the constructor of the type and return the instance binding any
         /// services in the constructor parameters.
         /// </summary>
         /// <param name="type">type to create</param>
         /// <param name="provider">services</param>
-        /// <param name="optional">if true, the service is not required</param>
         /// <returns>type instance</returns>
-        public static object InvokeConstructor(Type type, IServiceProvider provider, bool optional)
+        public static object InvokeConstructor(Type type, IServiceProvider provider)
         {
             ConstructorInfo constructor = type.GetConstructors().Single();
-            object[] arguments = BuildArguments(constructor, provider, optional);
+            object[] arguments = BuildArguments(constructor, provider);
             try
             {
                 return constructor.Invoke(arguments);
@@ -138,11 +335,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         /// <param name="method">method to invoke</param>
         /// <param name="instance">class instance or null if static</param>
         /// <param name="provider">services</param>
-        /// <param name="optional">if true, the service is not required</param>
         /// <returns>method return value</returns>
-        public static object Invoke(MethodBase method, object instance, IServiceProvider provider, bool optional)
+        public static object Invoke(MethodBase method, object instance, IServiceProvider provider)
         {
-            object[] arguments = BuildArguments(method, provider, optional);
+            object[] arguments = BuildArguments(method, provider);
             try
             {
                 return method.Invoke(instance, arguments);
@@ -154,14 +350,20 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             }
         }
 
-        private static object[] BuildArguments(MethodBase methodBase, IServiceProvider services, bool optional)
+        private static object[] BuildArguments(MethodBase methodBase, IServiceProvider services)
         {
             ParameterInfo[] parameters = methodBase.GetParameters();
             object[] arguments = new object[parameters.Length];
             for (int i = 0; i < parameters.Length; i++)
             {
-                // The parameter will passed as null to allow for "optional" services. The invoked 
-                // method needs to check for possible null parameters.
+                // The service import attribute isn't necessary on parameters unless Optional property needs to be changed from the default of false.
+                bool optional = false;
+                ServiceImportAttribute attribute = parameters[i].GetCustomAttribute<ServiceImportAttribute>(inherit: false);
+                if (attribute is not null)
+                {
+                    optional = attribute.Optional;
+                }
+                // The parameter will passed as null to allow for "optional" services. The invoked method needs to check for possible null parameters.
                 arguments[i] = services.GetService(parameters[i].ParameterType);
                 if (arguments[i] is null && !optional)
                 {
index 2898669a4a17c6a8c0fcaf1507fcd483e056b1ec..70f2ebec1b9710a495015425cbc6384acd826f7b 100644 (file)
@@ -14,6 +14,7 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <summary>
         /// Console service
         /// </summary>
+        [ServiceImport]
         public IConsoleService Console { get; set; }
 
         /// <summary>
@@ -31,6 +32,15 @@ namespace Microsoft.Diagnostics.DebugServices
             Console.Write(message);
         }
 
+        /// <summary>
+        /// Display a blank line
+        /// </summary>
+        protected void WriteLine()
+        {
+            Console.WriteLine();
+            Console.CancellationToken.ThrowIfCancellationRequested();
+        }
+
         /// <summary>
         /// Display line
         /// </summary>
index 3a1d79e5435726ea400c736bd58091cc93f5f496..116bffa479bfb06dcde5cbfcf63e2f23093525c0 100644 (file)
@@ -38,19 +38,10 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <param name="types">list of types to search</param>
         public static void AddCommands(this ICommandService commandService, IEnumerable<Type> types)
         {
-            foreach (Type type in types) {
+            foreach (Type type in types)
+            {
                 commandService.AddCommands(type);
             }
         }
-
-        /// <summary>
-        /// Add the commands and aliases attributes found in the type.
-        /// </summary>
-        /// <param name="commandService">command service instance</param>
-        /// <param name="type">Command type to search</param>
-        public static void AddCommands(this ICommandService commandService, Type type)
-        {
-            commandService.AddCommands(type, factory: null);
-        }
     }
 }
index 808dc1dabbf75955dcba1a9d236ede83599e5e47..89eded2e653f734aa3fff18de89df9dcb02b2f51 100644 (file)
@@ -8,6 +8,15 @@ namespace Microsoft.Diagnostics.DebugServices
 {
     public static class ConsoleServiceExtensions
     {
+        /// <summary>
+        /// Display a blank line
+        /// </summary>
+        /// <param name="console"></param>
+        public static void WriteLine(this IConsoleService console)
+        {
+            console.Write(Environment.NewLine);
+        }
+
         /// <summary>
         /// Display text
         /// </summary>
index c8e1736defa590f71cfeae811167d522c341a40f..3510998872edf53bafef0d1722979b4876177991 100644 (file)
@@ -11,25 +11,16 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <summary>
         /// Returns the current target
         /// </summary>
-        public static ITarget GetCurrentTarget(this IContextService contextService)
-        {
-            return contextService.Services.GetService<ITarget>();
-        }
+        public static ITarget GetCurrentTarget(this IContextService contextService) => contextService.Services.GetService<ITarget>();
 
         /// <summary>
         /// Returns the current thread
         /// </summary>
-        public static IThread GetCurrentThread(this IContextService contextService)
-        {
-            return contextService.Services.GetService<IThread>();
-        }
+        public static IThread GetCurrentThread(this IContextService contextService) => contextService.Services.GetService<IThread>();
 
         /// <summary>
         /// Returns the current runtime
         /// </summary>
-        public static IRuntime GetCurrentRuntime(this IContextService contextService)
-        {
-            return contextService.Services.GetService<IRuntime>();
-        }
+        public static IRuntime GetCurrentRuntime(this IContextService contextService) => contextService.Services.GetService<IRuntime>();
     }
 }
index 41f01735c79a8e69828f295f44f288fcaf220f8a..eabc327d6933368fc6cb74afc32ddd39b8b031fa 100644 (file)
@@ -26,4 +26,25 @@ namespace Microsoft.Diagnostics.DebugServices
         {
         }
     }
+
+    /// <summary>
+    /// Thrown if a command is not supported on the configuration, platform or runtime
+    /// </summary>
+    public class CommandNotSupportedException : DiagnosticsException
+    {
+        public CommandNotSupportedException()
+            : base()
+        {
+        }
+
+        public CommandNotSupportedException(string message)
+            : base(message)
+        {
+        }
+
+        public CommandNotSupportedException(string message, Exception innerException)
+            : base(message, innerException)
+        {
+        }
+    }
 }
index 7b46438428b64a2ecca435ccfb9dcf871738b576..b29484ae113c8a8ecb6404e36c56375e471f24ef 100644 (file)
@@ -21,8 +21,7 @@ namespace Microsoft.Diagnostics.DebugServices
         /// Add the commands and aliases attributes found in the type.
         /// </summary>
         /// <param name="type">Command type to search</param>
-        /// <param name="factory">function to create command instance</param>
-        void AddCommands(Type type, Func<IServiceProvider, object> factory);
+        void AddCommands(Type type);
 
         /// <summary>
         /// Displays the help for a command
index 1ecf28447942fe87a3ee096b316708b8b15438e7..764c3aceddc8054590d96270b77e29af5bea2bc0 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Threading;
 
 namespace Microsoft.Diagnostics.DebugServices
 {
diff --git a/src/Microsoft.Diagnostics.DebugServices/IField.cs b/src/Microsoft.Diagnostics.DebugServices/IField.cs
new file mode 100644 (file)
index 0000000..a8668e5
--- /dev/null
@@ -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.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Describes a field in a type (IType)
+    /// </summary>
+    public interface IField
+    {
+        /// <summary>
+        /// The type this field belongs
+        /// </summary>
+        IType Type { get; }
+
+        /// <summary>
+        /// The name of the field
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// The offset from the beginning of the instance
+        /// </summary>
+        uint Offset { get; }
+    }
+}
\ No newline at end of file
index 4c60e6d15ded3684b95439a7ee6a9eb57f796baf..c9593a3e41a7afb64b1cb707c6641beadcfb71c9 100644 (file)
@@ -25,10 +25,15 @@ namespace Microsoft.Diagnostics.DebugServices
     public interface IHost
     {
         /// <summary>
-        /// Invoked on hosting debugger or dotnet-dump shutdown
+        /// Fires on hosting debugger or dotnet-dump shutdown
         /// </summary>
         IServiceEvent OnShutdownEvent { get; }
 
+        /// <summary>
+        /// Fires when an new target is created.
+        /// </summary>
+        IServiceEvent<ITarget> OnTargetCreate { get; }
+
         /// <summary>
         /// Returns the hosting debugger type
         /// </summary>
@@ -43,11 +48,5 @@ namespace Microsoft.Diagnostics.DebugServices
         /// Enumerates all the targets
         /// </summary>
         IEnumerable<ITarget> EnumerateTargets();
-
-        /// <summary>
-        /// Destroys/closes the specified target instance
-        /// </summary>
-        /// <param name="target">target instance</param>
-        void DestroyTarget(ITarget target);
     }
 }
index 0fe8c48b44d7551388bd0a5aa63c2a132b06a41f..1cde6e6a5c918ac6a719da80e74cb6615767d5cd 100644 (file)
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using System;
 
 namespace Microsoft.Diagnostics.DebugServices
 {
@@ -27,5 +26,13 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <param name="address">address of symbol</param>
         /// <returns>true if found</returns>
         bool TryGetSymbolAddress(string name, out ulong address);
+
+        /// <summary>
+        /// Searches for a type by name
+        /// </summary>
+        /// <param name="typeName">type name to find</param>
+        /// <param name="type">returned type if found</param>
+        /// <returns>true if type found</returns>
+        bool TryGetType(string typeName, out IType type);
     }
 }
index 56bd7723ea8ed7279fc1b2f40da6e2f899654e4e..3c146a6c93866c13437e4cf7e7b725a1fa3f77f9 100644 (file)
@@ -11,10 +11,11 @@ namespace Microsoft.Diagnostics.DebugServices
     /// </summary>
     public enum RuntimeType
     {
-        Desktop     = 0,
-        NetCore     = 1,
-        SingleFile  = 2,
-        Unknown     = 3
+        Unknown     = 0,
+        Desktop     = 1,
+        NetCore     = 2,
+        SingleFile  = 3,
+        Other       = 4
     }
 
     /// <summary>
diff --git a/src/Microsoft.Diagnostics.DebugServices/IRuntimeProvider.cs b/src/Microsoft.Diagnostics.DebugServices/IRuntimeProvider.cs
new file mode 100644 (file)
index 0000000..8632ea4
--- /dev/null
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Provides the runtime information to the runtime service
+    /// </summary>
+    public interface IRuntimeProvider
+    {
+        /// <summary>
+        /// Returns the list of runtimes in the target
+        /// </summary>
+        /// <param name="startingRuntimeId">The starting runtime id for this provider</param>
+        IEnumerable<IRuntime> EnumerateRuntimes(int startingRuntimeId);
+    }
+}
index bcbaed50adfcfec8a35d3041cadf6aedd3c97f74..7c171b6c74f15f8a006144e4afe84abe2594780e 100644 (file)
@@ -31,4 +31,30 @@ namespace Microsoft.Diagnostics.DebugServices
         /// </summary>
         void Fire();
     }
+
+    /// <summary>
+    /// An event interface with one parameter.
+    /// </summary>
+    public interface IServiceEvent<T>
+    {
+        /// <summary>
+        /// Register for the event callback. Puts the new callback at the end of the list.
+        /// </summary>
+        /// <param name="callback">callback delegate</param>
+        /// <returns>An opaque IDisposable that will unregister the callback when disposed</returns>
+        IDisposable Register(Action<T> callback);
+
+        /// <summary>
+        /// Register for the event callback. Puts the new callback at the end of the list. Automatically 
+        /// removed from the event list when fired.
+        /// </summary>
+        /// <param name="callback">callback delegate</param>
+        /// <returns>An opaque IDisposable that will unregister the callback when disposed</returns>
+        IDisposable RegisterOneShot(Action<T> callback);
+
+        /// <summary>
+        /// Fires the event
+        /// </summary>
+        void Fire(T parameter);
+    }
 }
diff --git a/src/Microsoft.Diagnostics.DebugServices/IServiceManager.cs b/src/Microsoft.Diagnostics.DebugServices/IServiceManager.cs
new file mode 100644 (file)
index 0000000..59e98f2
--- /dev/null
@@ -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.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    public interface IServiceManager
+    {
+        /// <summary>
+        /// Creates a new service container factory with all the registered factories for the given scope.
+        /// </summary>
+        /// <param name="scope">global, per-target, per-runtime, etc. service type</param>
+        /// <param name="parent">parent service provider to chain to</param>
+        /// <returns>IServiceContainerFactory instance</returns>
+        ServiceContainerFactory CreateServiceContainerFactory(ServiceScope scope, IServiceProvider parent);
+
+        /// <summary>
+        /// Get the provider factories for a type or interface.
+        /// </summary>
+        /// <param name="providerType">type or interface</param>
+        /// <returns>the provider factories for the type</returns>
+        IEnumerable<ServiceFactory> EnumerateProviderFactories(Type providerType);
+    }
+}
index 4043cb99ccb7592a28aba8b689894da1e7e0012b..03e78754e24eba970a6d17918304cab599b29b2c 100644 (file)
@@ -68,5 +68,10 @@ namespace Microsoft.Diagnostics.DebugServices
         /// Invoked when the target is destroyed.
         /// </summary>
         IServiceEvent OnDestroyEvent { get; }
+
+        /// <summary>
+        /// Cleans up the target and releases target's resources.
+        /// </summary>
+        void Destroy();
     }
 }
diff --git a/src/Microsoft.Diagnostics.DebugServices/IType.cs b/src/Microsoft.Diagnostics.DebugServices/IType.cs
new file mode 100644 (file)
index 0000000..90c8cf0
--- /dev/null
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Describes a native type in a module
+    /// </summary>
+    public interface IType
+    {
+        /// <summary>
+        /// Name of the type
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// The module of the type 
+        /// </summary>
+        IModule Module { get; }
+
+        /// <summary>
+        /// A list of all the fields in the type
+        /// </summary>
+        List<IField> Fields { get; }
+
+        /// <summary>
+        /// Get a field by name
+        /// </summary>
+        /// <param name="fieldName">name of the field to find</param>
+        /// <param name="field">the returned field if found</param>
+        /// <returns>true if found</returns>
+        bool TryGetField(string fieldName, out IField field);
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceContainer.cs
new file mode 100644 (file)
index 0000000..d11319b
--- /dev/null
@@ -0,0 +1,116 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// This service provider and container implementation caches the service instance. Calls
+    /// the service factory for the type if not already instantiated and cached and if no 
+    /// factory, chains to the parent service container.
+    ///
+    /// This implementations allows multiple instances of the same service type to be 
+    /// registered. They are queried by getting the IEnumerable of the service type. 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.
+    /// </summary>
+    public class ServiceContainer : IServiceProvider
+    {
+        private readonly IServiceProvider _parent;
+        private readonly Dictionary<Type, object> _instances;
+        private readonly Dictionary<Type, ServiceFactory> _factories;
+
+        /// <summary>
+        /// Build a service provider with parent provider and service factories
+        /// </summary>
+        /// <param name="parent">search this provider if service isn't found in this instance or null</param>
+        /// <param name="factories">service factories to initialize provider or null</param>
+        public ServiceContainer(IServiceProvider parent, Dictionary<Type, ServiceFactory> factories)
+        {
+            Debug.Assert(factories != null);
+            _parent = parent;
+            _factories = factories;
+            _instances = new Dictionary<Type, object>();
+        }
+
+        /// <summary>
+        /// Add a service instance. Multiple instances for the same type are not allowed.
+        /// </summary>
+        /// <param name="type">service type</param>
+        /// <param name="service">service instance (must derives from type)</param>
+        public void AddService(Type type, object service) => _instances.Add(type, service);
+
+        /// <summary>
+        /// Add a service instance. Multiple instances for the same type are not allowed.
+        /// </summary>
+        /// <typeparam name="T">type of service</typeparam>
+        /// <param name="instance">service instance (must derive from T)</param>
+        public void AddService<T>(T instance) => AddService(typeof(T), instance);
+
+        /// <summary>
+        /// Flushes the cached service instance for the specified type. Does not remove the service factory registered for the type.
+        /// </summary>
+        /// <param name="type">service type</param>
+        public void RemoveService(Type type) => _instances.Remove(type);
+
+        /// <summary>
+        /// Dispose of the instantiated services.
+        /// </summary>
+        public void DisposeServices()
+        {
+            foreach (object service in _instances.Values)
+            {
+                if (service is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+            _instances.Clear();
+        }
+
+        /// <summary>
+        /// Get the cached/instantiated service instance if one exists. Don't call the factory or parent to create.
+        /// </summary>
+        /// <param name="type">service type</param>
+        /// <param name="service">service instance (can be null)</param>
+        /// <returns>if true, found service</returns>
+        public bool TryGetCachedService(Type type, out object service)
+        {
+            Debug.Assert(type != null);
+            if (type == typeof(IServiceProvider))
+            {
+                service = this;
+                return true;
+            }
+            return _instances.TryGetValue(type, out service);
+        }
+
+        /// <summary>
+        /// Returns the instance of the service or returns null if service doesn't exist
+        /// </summary>
+        /// <param name="type">service type</param>
+        /// <returns>service instance or null</returns>
+        public object GetService(Type type)
+        {
+            if (TryGetCachedService(type, out object service))
+            {
+                return service;
+            }
+            if (_factories.TryGetValue(type, out ServiceFactory factory))
+            {
+                service = factory(this);
+                _instances.Add(type, service);
+            }
+            else
+            {
+                service = _parent?.GetService(type);
+            }
+            return service;
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceContainerFactory.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceContainerFactory.cs
new file mode 100644 (file)
index 0000000..a96a3f0
--- /dev/null
@@ -0,0 +1,101 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// The method used to create a service instance
+    /// </summary>
+    /// <param name="provider">service provider instance that this factory is registered to.</param>
+    /// <returns>service instance</returns>
+    public delegate object ServiceFactory(IServiceProvider provider);
+
+    /// <summary>
+    /// </summary>
+    public class ServiceContainerFactory
+    {
+        private readonly Dictionary<Type, ServiceFactory> _factories;
+        private readonly IServiceProvider _parent;
+        private bool _finalized;
+
+        /// <summary>
+        /// Build a service container factory with parent provider and service factories
+        /// </summary>
+        /// <param name="parent">search this provider if service isn't found in this instance or null</param>
+        /// <param name="factories">service factories to initialize provider or null</param>
+        public ServiceContainerFactory(IServiceProvider parent, IDictionary<Type, ServiceFactory> factories)
+        {
+            Debug.Assert(factories != null);
+            _parent = parent;
+            _factories = new Dictionary<Type, ServiceFactory>(factories);
+        }
+
+        /// <summary>
+        /// Add a service factory.
+        /// </summary>
+        /// <param name="type">service type or interface</param>
+        /// <param name="factory">function to create service instance</param>
+        /// <exception cref="ArgumentNullException">thrown if type or factory is null</exception>
+        /// <exception cref="InvalidOperationException">thrown if factory has been finalized</exception>
+        public void AddServiceFactory(Type type, ServiceFactory factory)
+        {
+            if (type is null) throw new ArgumentNullException(nameof(type));
+            if (factory is null) throw new ArgumentNullException(nameof(factory));
+            if (_finalized) throw new InvalidOperationException();
+            _factories.Add(type, factory);
+        }
+
+        /// <summary>
+        /// Add a service factory.
+        /// </summary>
+        /// <typeparam name="T">service type</typeparam>
+        /// <param name="factory">function to create service instance</param>
+        public void AddServiceFactory<T>(ServiceFactory factory) => AddServiceFactory(typeof(T), factory);
+
+        /// <summary>
+        /// Removes the factory for the specified type.
+        /// </summary>
+        /// <param name="type">service type</param>
+        /// <exception cref="ArgumentNullException">thrown if type is null</exception>
+        /// <exception cref="InvalidOperationException">thrown if factory has been finalized</exception>
+        public void RemoveServiceFactory(Type type)
+        {
+            if (type is null) throw new ArgumentNullException(nameof(type));
+            if (_finalized) throw new InvalidOperationException();
+            _factories.Remove(type);
+        }
+
+        /// <summary>
+        /// Remove a service factory.
+        /// </summary>
+        /// <typeparam name="T">service type</typeparam>
+        public void RemoveServiceFactory<T>() => RemoveServiceFactory(typeof(T));
+
+        /// <summary>
+        /// Creates a new service container/provider instance and marks this factory as finalized. No more factories can be added or removed.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">thrown if factory has not been finalized</exception>
+        /// <returns>service container/provider instance</returns>
+        public ServiceContainer Build()
+        {
+            _finalized = true;
+            return new ServiceContainer(_parent, _factories);
+        }
+
+        /// <summary>
+        /// Creates a copy of the container factory with the service factories and the parent service provider.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">thrown if factory has not been finalized</exception>
+        /// <returns>clone</returns>
+        public ServiceContainerFactory Clone()
+        {
+            if (!_finalized) throw new InvalidOperationException();
+            return new ServiceContainerFactory(_parent, _factories);
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceExportAttribute.cs
new file mode 100644 (file)
index 0000000..c2df9ec
--- /dev/null
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    public enum ServiceScope
+    {
+        Global,
+        Context,
+        Provider,
+        Target,
+        Module,
+        Thread,
+        Runtime,
+        Max
+    }
+
+    /// <summary>
+    /// Marks classes or methods (service factories) as services
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+    public class ServiceExportAttribute : Attribute
+    {
+        /// <summary>
+        /// 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.
+        /// </summary>
+        public Type Type { get; set; }
+
+        /// <summary>
+        /// The scope of the service (global, per-target, per-context, per-runtime, etc).
+        /// </summary>
+        public ServiceScope Scope { get; set; }
+
+        /// <summary>
+        /// Default constructor.
+        /// </summary>
+        public ServiceExportAttribute()
+        {
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceImportAttribute.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceImportAttribute.cs
new file mode 100644 (file)
index 0000000..0c5e0e4
--- /dev/null
@@ -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.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Marks properties or methods to import another service when a service is instantiated.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
+    public class ServiceImportAttribute : Attribute
+    {
+        /// <summary>
+        /// If true, the service is optional and can even up null.
+        /// </summary>
+        public bool Optional { get; set; }
+
+        /// <summary>
+        /// Default constructor.
+        /// </summary>
+        public ServiceImportAttribute()
+        {
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ServiceManagerExtensions.cs b/src/Microsoft.Diagnostics.DebugServices/ServiceManagerExtensions.cs
new file mode 100644 (file)
index 0000000..79ebc83
--- /dev/null
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    public static class ServiceManagerExtensions
+    {
+        /// <summary>
+        /// Creates a new service container with all the registered factories for the given scope.
+        /// </summary>
+        /// <param name="serviceManager">service manager instance</param>
+        /// <param name="scope">global, per-target, per-runtime, etc. service type</param>
+        /// <param name="parent">parent service provider to chain to</param>
+        /// <returns>IServiceContainer instance</returns>
+        public static ServiceContainer CreateServiceContainer(this IServiceManager serviceManager, ServiceScope scope, IServiceProvider parent)
+        {
+            ServiceContainerFactory containerFactory = serviceManager.CreateServiceContainerFactory(scope, parent);
+            return containerFactory.Build();
+        }
+    }
+}
index b994d4d139c5b3a01d752aad62a308d36c7384e4..c3cfb7d957f388ff87ba60485348fe62cd2ee49b 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Collections.Generic;
 
 namespace Microsoft.Diagnostics.DebugServices
 {
@@ -14,9 +13,6 @@ namespace Microsoft.Diagnostics.DebugServices
         /// </summary>
         /// <typeparam name="T">service type</typeparam>
         /// <returns>service instance or null</returns>
-        public static T GetService<T>(this IServiceProvider serviceProvider)
-        {
-            return (T)serviceProvider.GetService(typeof(T));
-        }
+        public static T GetService<T>(this IServiceProvider serviceProvider) => (T)serviceProvider.GetService(typeof(T));
     }
 }
index a8beb0692b9e5a104d5d1b6e42b8a065abce8b26..4824217a27f53afbcd9b4750f8c96d92c97ed0cd 100644 (file)
@@ -32,16 +32,5 @@ namespace Microsoft.Diagnostics.DebugServices
             }
             throw new PlatformNotSupportedException(target.OperatingSystem.ToString());
         }
-
-        /// <summary>
-        /// Registers an object to be disposed when target is destroyed.
-        /// </summary>
-        /// <param name="target">target instance</param>
-        /// <param name="disposable">object to be disposed or null</param>
-        /// <returns>IDisposable to unregister this event or null</returns>
-        public static IDisposable DisposeOnDestroy(this ITarget target, IDisposable disposable)
-        {
-            return disposable != null ? target.OnDestroyEvent.Register(() => disposable.Dispose()) : null;
-        }
     }
 }
index 40b9662a56445484de0993c4b4f4ae150d1d6ff1..74dcadbc7770b9d1fa3717d78f937190f8d591a9 100644 (file)
@@ -16,7 +16,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         private readonly ClrRuntime _clr;
         private readonly ClrHeap _heap;
 
-        public ClrMDHelper(ClrRuntime clr)
+        [ServiceExport(Scope = ServiceScope.Runtime)]
+        public static ClrMDHelper Create([ServiceImport(Optional = true)] ClrRuntime clrRuntime)
+        {
+            return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null;
+        }
+
+        private ClrMDHelper(ClrRuntime clr)
         {
             Debug.Assert(clr != null);
             _clr = clr;
index 4c253cdfba3f7290fd980a540fba8016d5be90cb..29e4e6414975fdf6e9a73a657f50a58f2382cdd5 100644 (file)
@@ -13,8 +13,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "clrmodules", Help = "Lists the managed modules in the process.")]
     public class ClrModulesCommand : CommandBase
     {
+        [ServiceImport(Optional = true)]
         public ClrRuntime Runtime { get; set; }
 
+        [ServiceImport]
         public IModuleService ModuleService { get; set; }
 
         [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on module name (path not included).")]
index d09bf213fdae5a69e1561cd7e24195e1e4f40b75..d01136c92fc9ffd14d0ee6b09cf889f1a99b453d 100644 (file)
@@ -49,6 +49,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             $"Show each stack that includes an object at a specific address, and include fields: !{CommandName} --address 0x000001264adce778 --fields";
 
         /// <summary>Gets the runtime for the process.  Set by the command framework.</summary>
+        [ServiceImport(Optional = true)]
         public ClrRuntime? Runtime { get; set; }
 
         /// <summary>Gets whether to only show stacks that include the object with the specified address.</summary>
index fc83c22c98cfd2865dee33ddb4e4836228a84cf9..e23856b3e9a6f97175e1d0f70ea51ebf372a3307 100644 (file)
@@ -14,6 +14,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Argument(Help = "The address of a ConcurrentDictionary object.")]
         public string Address { get; set; }
 
+        [ServiceImport]
         public ClrRuntime Runtime { get; set; }
 
         public override void ExtensionInvoke()
index ea7b0a70df8b89b0ca1bac714f26578b66f6e6fd..0df3ea4803e539d037d4de5ee348e7d556552c5b 100644 (file)
@@ -14,6 +14,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Argument(Help = "The address of a ConcurrentQueue object.")]
         public string Address { get; set; }
 
+        [ServiceImport]
         public ClrRuntime Runtime { get; set; }
 
         public override void ExtensionInvoke()
index c2f4c099de72aea355647202813c762b0b09560c..49b07faabc860a0e36b9513015cc74d103e05167 100644 (file)
@@ -11,6 +11,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         /// <summary>
         /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD.
         /// </summary>
+        [ServiceImport(Optional = true)]
         public ClrMDHelper Helper { get; set; }
 
         public override void Invoke()
index 221f469a185f00921a6f715d41a648931a7cdd9e..b8764508bda6bfa2044ff4a1ec0414611db8b39c 100644 (file)
@@ -10,6 +10,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "logclose", DefaultOptions = "--disable", Help = "Disables console file logging.", Flags = CommandFlags.Global)]
     public class ConsoleLoggingCommand : CommandBase
     {
+        [ServiceImport(Optional = true)]
         public IConsoleFileLoggingService FileLoggingService { get; set; }
 
         [Argument(Name = "path", Help = "Log file path.")]
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs
new file mode 100644 (file)
index 0000000..1445b34
--- /dev/null
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.DebugServices;
+using System;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    [Command(Name = "help", Help = "Displays help for a command.", Flags = CommandFlags.Global)]
+    public class HelpCommand : CommandBase
+    {
+        [Argument(Help = "Command to find help.")]
+        public string Command { get; set; }
+
+        [ServiceImport]
+        public ICommandService CommandService { get; set; }
+
+        [ServiceImport]
+        public IServiceProvider Services { get; set; }
+
+        public override void Invoke()
+        {
+            if (!CommandService.DisplayHelp(Command, Services))
+            {
+                throw new NotSupportedException($"Help for {Command} not found");
+            }
+        }
+    }
+}
index 4732ab1bb11b07bf6ddc3f0f2ff44c5b158b6896..9b63dbe0cb8701505cfabc76f1b15dfabda3c216 100644 (file)
@@ -13,6 +13,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "logging", Help = "Enables/disables internal diagnostic logging.", Flags = CommandFlags.Global)]
     public class LoggingCommand : CommandBase
     {
+        [ServiceImport(Optional = true)]
         public IDiagnosticLoggingService DiagnosticLoggingService { get; set; }
 
         [Argument(Name = "path", Help = "Log file path.")]
index 74fd7a9cab67c15f56ab4ae726727ce4abb80853..5836f55b2f4555b8700a36a85e7e1d3809da0177 100644 (file)
@@ -31,6 +31,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "--address", Aliases = new string[] { "-a" }, Help = "Lookup address in module list.")]
         public ulong? Address { get; set; }
 
+        [ServiceImport]
         public IModuleService ModuleService { get; set; }
 
         public override void Invoke()
@@ -97,8 +98,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             }
         }
 
+        [ServiceImport]
         public ITarget Target { get; set; }
 
+        [ServiceImport]
         public IMemoryService MemoryService { get; set; }
 
         void DisplaySegments(IModule module)
index d8d3bc023349fb284de95598936b213fcd0bffc3..135522aed03ea6e0749d2e2972d0b9019ec54f63 100644 (file)
@@ -10,8 +10,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "registers", Aliases = new string[] { "r" }, Help = "Displays the thread's registers.")]
     public class RegistersCommand : CommandBase
     {
+        [ServiceImport]
         public IThreadService ThreadService { get; set; }
 
+        [ServiceImport]
         public IThread CurrentThread { get; set; }
 
         [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")]
index b88dd18efaf1047b5e31d1e334f2cced776c651a..bd5f4ab2bd2cc1b36dab724b3ce1eee27527041e 100644 (file)
@@ -12,12 +12,18 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "runtimes", Help = "Lists the runtimes in the target or changes the default runtime.")]
     public class RuntimesCommand : CommandBase
     {
+        [ServiceImport]
         public IRuntimeService RuntimeService { get; set; }
 
+        [ServiceImport]
         public IContextService ContextService { get; set; }
 
+        [ServiceImport]
         public ITarget Target { get; set; }
 
+        [Argument(Help = "Switch to the runtime by id.")]
+        public int? Id { get; set; }
+
         [Option(Name = "--netfx", Aliases = new string[] { "-netfx", "-f" }, Help = "Switches to the desktop .NET Framework if exists.")]
         public bool NetFx { get; set; }
 
@@ -39,22 +45,28 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                         NetCore && runtime.RuntimeType == RuntimeType.NetCore)
                     {
                         ContextService.SetCurrentRuntime(runtime.Id);
-                        WriteLine("Switched to {0} runtime successfully", name);
+                        WriteLine($"Switched to {name} runtime successfully");
                         return;
                     }
                 }
-                WriteLineError("The {0} runtime is not loaded", name);
+                WriteLineError($"The {name} runtime is not loaded");
+            }
+            else if (Id.HasValue)
+            {
+                ContextService.SetCurrentRuntime(Id.Value);
+                WriteLine($"Switched to runtime #{Id.Value} successfully");
             }
             else
             {
-                // Display the current runtime star ("*") only if there is more than one runtime
+                // Display the current runtime star ("*") only if there is more than one runtime and it is the current one
                 bool displayStar = RuntimeService.EnumerateRuntimes().Count() > 1;
+                IRuntime currentRuntime = ContextService.GetCurrentRuntime();
 
                 foreach (IRuntime runtime in RuntimeService.EnumerateRuntimes())
                 {
-                    string current = displayStar ? (runtime == ContextService.GetCurrentRuntime() ? "*" : " ") : "";
+                    string current = displayStar ? (runtime == currentRuntime ? "*" : " ") : "";
                     Write(current);
-                    Write(runtime.ToString());
+                    WriteLine(runtime.ToString());
                     ClrInfo clrInfo = runtime.Services.GetService<ClrInfo>();
                     if (clrInfo is not null)
                     {
@@ -70,7 +82,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         }
     }
 
-    public static class Utilities
+    public static class CommandUtilities
     {
         public static string ToHex(this ImmutableArray<byte> array) => string.Concat(array.Select((b) => b.ToString("x2")));
     }
index 1191419d5973563a0c01cdef21d51b515c971ac3..66d1cac5d009c54bf916f707a16dabd70ec1a579 100644 (file)
@@ -10,6 +10,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "setclrpath", Help = "Sets the path to load coreclr DAC/DBI files.")]
     public class SetClrPath: CommandBase
     {
+        [ServiceImport(Optional = true)]
         public IRuntime Runtime { get; set; }
 
         [Argument(Name = "path", Help = "Runtime directory path.")]
index 46484519d135bd5c7f57a65a8fc0a41d94e5ea97..28cadedcf8452e9d4531838db27a68de20f38089 100644 (file)
@@ -18,8 +18,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         Flags = CommandFlags.Global)]
     public class SetSymbolServerCommand : CommandBase
     {
+        [ServiceImport]
         public ISymbolService SymbolService { get; set; }
 
+        [ServiceImport(Optional = true)]
         public IModuleService ModuleService { get; set; }
 
         [Option(Name = "--ms", Aliases = new string[] { "-ms" }, Help = "Use the public Microsoft symbol server.")]
@@ -89,7 +91,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             {
                 SymbolService.AddDirectoryPath(Directory);
             }
-            if (LoadSymbols)
+            if (LoadSymbols && ModuleService is not null)
             {
                 foreach (IModule module in ModuleService.EnumerateModules())
                 {
index cbc7414b63d48310ec44745d01622d2a0fdcf5d4..eaef61634a015b54066662230fc824a35635f674 100644 (file)
@@ -10,8 +10,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "sosstatus", Help = "Displays internal status or resets the internal cached state.")]
     public class StatusCommand : CommandBase
     {
+        [ServiceImport]
         public ITarget Target { get; set; }
 
+        [ServiceImport]
         public ISymbolService SymbolService { get; set; }
 
         [Option(Name = "-reset", Help = "Reset all the cached internal state.")]
index 9f700c2928e15ab941d3600662ea33cc0d31268f..21ab894e5673eb7f5c039e27186a863758e4460f 100644 (file)
@@ -18,10 +18,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")]
         public bool Verbose { get; set; }
 
+        [ServiceImport(Optional = true)]
         public IThread CurrentThread { get; set; }
 
+        [ServiceImport]
         public IThreadService ThreadService { get; set; }
 
+        [ServiceImport]
         public IContextService ContextService { get; set; }
 
         public override void Invoke()
index 43db291e18f8a49ce408345cdccfb9e3b261325a..16b12c6b62d7b2f06652d05d3dad1a8a38254678 100644 (file)
@@ -12,6 +12,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "parallelstacks", Aliases = new string[] { "pstacks" }, Help = "Displays the merged threads stack similarly to the Visual Studio 'Parallel Stacks' panel.")]
     public class ParallelStacksCommand : ExtensionCommandBase
     {
+        [ServiceImport]
         public ClrRuntime Runtime { get; set; }
 
         [Option(Name = "--allthreads", Aliases = new string[] { "-a" }, Help = "Displays all threads per group instead of at most 4 by default.")]
diff --git a/src/Microsoft.Diagnostics.Repl/HelpCommand.cs b/src/Microsoft.Diagnostics.Repl/HelpCommand.cs
deleted file mode 100644 (file)
index 30f1b63..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.Diagnostics.DebugServices;
-using System;
-
-namespace Microsoft.Diagnostics.Repl
-{
-    [Command(Name = "help", Help = "Displays help for a command.", Flags = CommandFlags.Global | CommandFlags.Manual)]
-    public class HelpCommand : CommandBase
-    {
-        [Argument(Help = "Command to find help.")]
-        public string Command { get; set; }
-
-        private readonly ICommandService _commandService;
-        private readonly IServiceProvider _services;
-
-        public HelpCommand(ICommandService commandService, IServiceProvider services)
-        {
-            _commandService = commandService;
-            _services = services;
-        }
-
-        public override void Invoke()
-        {
-            if (!_commandService.DisplayHelp(Command, _services))
-            {
-                throw new NotSupportedException($"Help for {Command} not found");
-            }
-        }
-    }
-}
index 537275ce3a7ac9a2a82c690de3a7f8ba77338960..99a194bad709ea5f2af43247fa3c657c3ece42e0 100644 (file)
@@ -1,6 +1,7 @@
 ï»¿using Microsoft.Diagnostics.DebugServices;
 using System;
 using System.Collections.Immutable;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -20,19 +21,21 @@ namespace Microsoft.Diagnostics.TestHelpers
         public TestDataWriter()
         {
             Root = new XElement("TestData");
-            Root.Add(new XElement("Version", "1.0.1"));
+            Root.Add(new XElement("Version", "1.0.2"));
             Target = new XElement("Target");
             Root.Add(Target);
         }
 
-        public void Build(ITarget target)
+        public void Build(IServiceProvider services)
         {
+            ITarget target = services.GetService<ITarget>();
+            Debug.Assert(target is not null);
             AddMembers(Target, typeof(ITarget), target, nameof(ITarget.Id), nameof(ITarget.GetTempDirectory));
 
             var modulesElement = new XElement("Modules");
             Target.Add(modulesElement);
 
-            var moduleService = target.Services.GetService<IModuleService>();
+            var moduleService = services.GetService<IModuleService>();
             string runtimeModuleName = target.GetPlatformModuleName("coreclr");
             foreach (IModule module in moduleService.EnumerateModules())
             {
@@ -44,7 +47,7 @@ namespace Microsoft.Diagnostics.TestHelpers
             var threadsElement = new XElement("Threads");
             Target.Add(threadsElement);
 
-            var threadService = target.Services.GetService<IThreadService>();
+            var threadService = services.GetService<IThreadService>();
             var registerIndexes = new int[] { threadService.InstructionPointerIndex, threadService.StackPointerIndex, threadService.FramePointerIndex };
             foreach (IThread thread in threadService.EnumerateThreads())
             {
@@ -73,7 +76,7 @@ namespace Microsoft.Diagnostics.TestHelpers
             var runtimesElement = new XElement("Runtimes");
             Target.Add(runtimesElement);
 
-            var runtimeService = target.Services.GetService<IRuntimeService>();
+            var runtimeService = services.GetService<IRuntimeService>();
             foreach (IRuntime runtime in runtimeService.EnumerateRuntimes())
             {
                 var runtimeElement = new XElement("Runtime");
@@ -93,7 +96,12 @@ namespace Microsoft.Diagnostics.TestHelpers
 
         private void AddModuleMembers(XElement element, IModule module, string symbolModuleName)
         {
-            AddMembers(element, typeof(IModule), module, nameof(IModule.ModuleIndex), nameof(IModule.GetPdbFileInfos), nameof(IModule.GetVersionString), nameof(IModule.GetSymbolFileName));
+            AddMembers(element, typeof(IModule), module,
+                nameof(IModule.ModuleIndex),
+                nameof(IModule.GetPdbFileInfos),
+                nameof(IModule.GetVersionString),
+                nameof(IModule.GetSymbolFileName),
+                nameof(IModule.LoadSymbols));
 
             if (symbolModuleName != null && IsModuleEqual(module, symbolModuleName))
             {
index 2df8072ee9e98ddf98bee0dbd51161753fc6303d..49d93217c97f7db89ba682f6e41036a5af03830c 100644 (file)
@@ -10,8 +10,8 @@ namespace Microsoft.Diagnostics.TestHelpers
 {
     public class TestDump : TestHost, IHost
     {
-        private readonly ServiceProvider _serviceProvider;
-        private readonly ContextService _contextService;
+        private readonly ServiceManager _serviceManager;
+        private readonly ServiceContainer _serviceContainer;
         private readonly SymbolService _symbolService;
         private DataTarget _dataTarget;
         private int _targetIdFactory;
@@ -19,11 +19,23 @@ namespace Microsoft.Diagnostics.TestHelpers
         public TestDump(TestConfiguration config)
             : base(config)
         {
-            _serviceProvider = new ServiceProvider();
-            _contextService = new ContextService(this);
+            _serviceManager = new ServiceManager();
+
+            // Register all the services and commands in the Microsoft.Diagnostics.DebugServices.Implementation assembly
+            _serviceManager.RegisterAssembly(typeof(Target).Assembly);
+
+            // Loading extensions or adding service factories not allowed after this point.
+            _serviceManager.FinalizeServices();
+
+            _serviceContainer = _serviceManager.CreateServiceContainer(ServiceScope.Global, parent: null);
+            _serviceContainer.AddService<IServiceManager>(_serviceManager);
+            _serviceContainer.AddService<IHost>(this);
+
+            var contextService = new ContextService(this);
+            _serviceContainer.AddService<IContextService>(contextService);
+
             _symbolService = new SymbolService(this);
-            _serviceProvider.AddService<IContextService>(_contextService);
-            _serviceProvider.AddService<ISymbolService>(_symbolService);
+            _serviceContainer.AddService<ISymbolService>(_symbolService);
 
             // Automatically enable symbol server support
             _symbolService.AddSymbolServer(msdl: true, symweb: false, timeoutInMinutes: 6, retryCount: 5);
@@ -46,25 +58,13 @@ namespace Microsoft.Diagnostics.TestHelpers
 
         public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
 
-        HostType IHost.HostType => HostType.DotnetDump;
+        public IServiceEvent<ITarget> OnTargetCreate { get; } = new ServiceEvent<ITarget>();
 
-        IServiceProvider IHost.Services => _serviceProvider;
+        public HostType HostType => HostType.DotnetDump;
 
-        IEnumerable<ITarget> IHost.EnumerateTargets() => Target != null ? new ITarget[] { Target } : Array.Empty<ITarget>();
+        public IServiceProvider Services => _serviceContainer;
 
-        void IHost.DestroyTarget(ITarget target)
-        {
-            if (target == null) {
-                throw new ArgumentNullException(nameof(target));
-            }
-            if (target == Target)
-            {
-                _contextService.ClearCurrentTarget();
-                if (target is IDisposable disposable) {
-                    disposable.Dispose();
-                }
-            }
-        }
+        public IEnumerable <ITarget> EnumerateTargets() => Target != null ? new ITarget[] { Target } : Array.Empty<ITarget>();
 
         #endregion
     }
index 92ba06039e2896b0cb01b863454040139dc90526..9bd22981ab465d7fdd3a9c4ff863934d02dc8b52 100644 (file)
@@ -1,4 +1,6 @@
-using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.DebugServices;
+using System;
+using System.IO;
 
 namespace Microsoft.Diagnostics.TestHelpers
 {
index 849d47294fe6bfd57fd6313f84ffa650ecc770e6..c6ad8a6b7e3943d9ceb95c7045fe1a16b0412983 100644 (file)
@@ -23,7 +23,7 @@ namespace SOS.Extensions
             _debuggerServices = debuggerServices;
         }
 
-        public override IThread GetCurrentThread()
+        protected override IThread GetCurrentThread()
         {
             HResult hr = _debuggerServices.GetCurrentThreadId(out uint threadId);
             if (hr != HResult.S_OK)
@@ -31,7 +31,10 @@ namespace SOS.Extensions
                 Trace.TraceError("GetCurrentThreadId() FAILED {0:X8}", hr);
                 return null;
             }
-            return ThreadService?.GetThreadFromId(threadId);
+            IThread currentThread = ThreadService?.GetThreadFromId(threadId);
+            // This call fires the context change event if the thread obtain by the host debugger differs from the current thread
+            base.SetCurrentThread(currentThread);
+            return currentThread;
         }
 
         public override void SetCurrentThread(IThread thread)
index a6ebabb0c3264ed3f6a62e0487290484289a85b5..4cc700bf38ececc6252a0c7a9ce9ae5336157485 100644 (file)
@@ -325,6 +325,30 @@ namespace SOS
                 return VTable.GetOffsetBySymbol(Self, moduleIndex, symbolPtr, out address);
             }
         }
+        
+        public HResult GetTypeId(int moduleIndex, string typeName, out ulong typeId)
+        {
+            if (string.IsNullOrEmpty(typeName)) throw new ArgumentException(nameof(typeName));
+
+            byte[] typeNameBytes = Encoding.ASCII.GetBytes(typeName + "\0");
+            fixed (byte* typeNamePtr = typeNameBytes)
+            {
+                return VTable.GetTypeId(Self, moduleIndex, typeNamePtr, out typeId);
+            }
+        }
+
+        public HResult GetFieldOffset(int moduleIndex, ulong typeId, string typeName, string fieldName, out uint offset)
+        {
+            if (string.IsNullOrEmpty(fieldName)) throw new ArgumentException(nameof(fieldName));
+
+            byte[] typeNameBytes = Encoding.ASCII.GetBytes(typeName + "\0");
+            byte[] fieldNameBytes = Encoding.ASCII.GetBytes(fieldName + "\0");
+            fixed (byte* typeNamePtr = typeNameBytes)
+            fixed (byte *fieldNamePtr = fieldNameBytes)
+            {
+                return VTable.GetFieldOffset(Self, moduleIndex, typeNamePtr, typeId, fieldNamePtr, out offset);
+            }
+        }
 
         public int GetOutputWidth() => (int)VTable.GetOutputWidth(Self);
 
@@ -387,6 +411,8 @@ namespace SOS
             public readonly delegate* unmanaged[Stdcall]<IntPtr, byte*, uint, out uint, int> GetSymbolPath;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, int, ulong, byte*, int, out uint, out ulong, int> GetSymbolByOffset;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, int, byte*, out ulong, int> GetOffsetBySymbol;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, int, byte*, out ulong, HResult> GetTypeId;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, int, byte*, ulong, byte*, out uint, HResult> GetFieldOffset;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, uint> GetOutputWidth;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, uint*, int> SupportsDml;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, DEBUG_OUTPUT, byte*, void> OutputDmlString;
index 744d54c590ac2ec37d36ed4d53a65bd74ebeb1f1..dd1fc44d8d3b4de9471efdf222b7a723acd963d6 100644 (file)
@@ -37,14 +37,14 @@ namespace SOS.Extensions
 
         internal DebuggerServices DebuggerServices { get; private set; }
 
-        private readonly ServiceProvider _serviceProvider;
+        private readonly ServiceManager _serviceManager;
         private readonly CommandService _commandService;
         private readonly SymbolService _symbolService;
         private readonly HostWrapper _hostWrapper;
+        private ServiceContainer _serviceContainer;
         private ContextServiceFromDebuggerServices _contextService;
         private int _targetIdFactory;
         private ITarget _target;
-        private TargetWrapper _targetWrapper;
 
         /// <summary>
         /// Enable the assembly resolver to get the right versions in the same directory as this assembly.
@@ -106,20 +106,17 @@ namespace SOS.Extensions
 
         private HostServices()
         {
-            _serviceProvider = new ServiceProvider();
-            _serviceProvider.AddService<IDiagnosticLoggingService>(DiagnosticLoggingService.Instance);
-            _symbolService = new SymbolService(this);
-            _symbolService.DefaultTimeout = DefaultTimeout;
-            _symbolService.DefaultRetryCount = DefaultRetryCount;
+            _serviceManager = new ServiceManager();
             _commandService = new CommandService(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!ext" : null);
-            _commandService.AddCommands(new Assembly[] { typeof(HostServices).Assembly });
-            _commandService.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly });
+            _serviceManager.NotifyExtensionLoad.Register(_commandService.AddCommands);
 
-            _serviceProvider.AddService<IHost>(this);
-            _serviceProvider.AddService<ICommandService>(_commandService);
-            _serviceProvider.AddService<ISymbolService>(_symbolService);
+            _symbolService = new SymbolService(this)
+            {
+                DefaultTimeout = DefaultTimeout,
+                DefaultRetryCount = DefaultRetryCount
+            };
 
-            _hostWrapper = new HostWrapper(this, () => _targetWrapper);
+            _hostWrapper = new HostWrapper(this);
             _hostWrapper.ServiceWrapper.AddServiceWrapper(IID_IHostServices, this);
 
             VTableBuilder builder = AddInterface(IID_IHostServices, validate: false);
@@ -141,39 +138,20 @@ namespace SOS.Extensions
         {
             Trace.TraceInformation("HostServices.Destroy");
             _hostWrapper.ServiceWrapper.RemoveServiceWrapper(IID_IHostServices);
-            _hostWrapper.Release();
+            _hostWrapper.ReleaseWithCheck();
         }
 
         #region IHost
 
         public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
 
+        public IServiceEvent<ITarget> OnTargetCreate { get; } = new ServiceEvent<ITarget>();
+
         public HostType HostType => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? HostType.DbgEng : HostType.Lldb;
 
-        IServiceProvider IHost.Services => _serviceProvider;
+        public IServiceProvider Services => _serviceContainer;
 
-        IEnumerable<ITarget> IHost.EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty<ITarget>();
-
-        public void DestroyTarget(ITarget target)
-        {
-            if (target == null) {
-                throw new ArgumentNullException(nameof(target));
-            }
-            Trace.TraceInformation("IHost.DestroyTarget #{0}", target.Id);
-            if (target == _target)
-            {
-                _target = null;
-                if (_targetWrapper != null)
-                {
-                    _targetWrapper.Release();
-                    _targetWrapper = null;
-                }
-                _contextService.ClearCurrentTarget();
-                if (target is IDisposable disposable) {
-                    disposable.Dispose();
-                }
-            }
-        }
+        public IEnumerable<ITarget> EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty<ITarget>();
 
         #endregion
 
@@ -206,31 +184,50 @@ namespace SOS.Extensions
                 Trace.TraceError(ex.Message);
                 return HResult.E_NOINTERFACE;
             }
-            try
-            {
-                var remoteMemoryService = new RemoteMemoryService(iunk);
-                _serviceProvider.AddService<IRemoteMemoryService>(remoteMemoryService);
-            }
-            catch (InvalidCastException)
-            {
-            }
             HResult hr;
             try
             {
                 var consoleService = new ConsoleServiceFromDebuggerServices(DebuggerServices);
                 var fileLoggingConsoleService = new FileLoggingConsoleService(consoleService);
                 DiagnosticLoggingService.Instance.SetConsole(consoleService, fileLoggingConsoleService);
-                _serviceProvider.AddService<IConsoleService>(fileLoggingConsoleService);
-                _serviceProvider.AddService<IConsoleFileLoggingService>(fileLoggingConsoleService);
+
+                // Don't register everything in the SOSHost assembly; just the wrappers
+                _serviceManager.RegisterExportedServices(typeof(TargetWrapper));
+                _serviceManager.RegisterExportedServices(typeof(RuntimeWrapper));
+
+                // Register all the services and commands in the Microsoft.Diagnostics.DebugServices.Implementation assembly
+                _serviceManager.RegisterAssembly(typeof(Target).Assembly);
+
+                // Register all the services and commands in the SOS.Extensions (this) assembly
+                _serviceManager.RegisterAssembly(typeof(HostServices).Assembly);
+
+                // Register all the services and commands in the Microsoft.Diagnostics.ExtensionCommands assembly
+                _serviceManager.RegisterAssembly(typeof(ClrMDHelper).Assembly);
+
+                // Display any extension assembly loads on console
+                _serviceManager.NotifyExtensionLoad.Register((Assembly assembly) => fileLoggingConsoleService.WriteLine($"Loading extension {assembly.Location}"));
+
+                // Load any extra extensions in the search path
+                _serviceManager.LoadExtensions();
+
+                // Loading extensions or adding service factories not allowed after this point.
+                _serviceManager.FinalizeServices();
+
+                // Add all the global services to the global service container
+                _serviceContainer = _serviceManager.CreateServiceContainer(ServiceScope.Global, parent: null);
+                _serviceContainer.AddService<IServiceManager>(_serviceManager);
+                _serviceContainer.AddService<IHost>(this);
+                _serviceContainer.AddService<ICommandService>(_commandService);
+                _serviceContainer.AddService<ISymbolService>(_symbolService);
+                _serviceContainer.AddService<IConsoleService>(fileLoggingConsoleService);
+                _serviceContainer.AddService<IConsoleFileLoggingService>(fileLoggingConsoleService);
+                _serviceContainer.AddService<IDiagnosticLoggingService>(DiagnosticLoggingService.Instance);
 
                 _contextService = new ContextServiceFromDebuggerServices(this, DebuggerServices);
-                _serviceProvider.AddService<IContextService>(_contextService);
-                _serviceProvider.AddServiceFactory<IThreadUnwindService>(() => new ThreadUnwindServiceFromDebuggerServices(DebuggerServices));
+                _serviceContainer.AddService<IContextService>(_contextService);
 
-                _contextService.ServiceProvider.AddServiceFactory<ClrMDHelper>(() => {
-                    ClrRuntime clrRuntime = _contextService.Services.GetService<ClrRuntime>();
-                    return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null;
-                });
+                var threadUnwindService = new ThreadUnwindServiceFromDebuggerServices(DebuggerServices);
+                _serviceContainer.AddService<IThreadUnwindService>(threadUnwindService);
 
                 // Add each extension command to the native debugger
                 foreach ((string name, string help, IEnumerable<string> aliases) in _commandService.Commands)
@@ -247,6 +244,17 @@ namespace SOS.Extensions
                 Trace.TraceError(ex.ToString());
                 return HResult.E_FAIL;
             }
+            try
+            {
+                var remoteMemoryService = new RemoteMemoryService(iunk);
+                // This service needs another reference since it is implemented as part of IDebuggerServices and gets
+                // disposed in Uninitialize() below by the DisposeServices call.
+                remoteMemoryService.AddRef();
+                _serviceContainer.AddService<IRemoteMemoryService>(remoteMemoryService);
+            }
+            catch (InvalidCastException)
+            {
+            }
             hr = DebuggerServices.GetSymbolPath(out string symbolPath);
             if (hr == HResult.S_OK)
             {
@@ -271,10 +279,9 @@ namespace SOS.Extensions
             }
             try
             {
-                _target = new TargetFromDebuggerServices(DebuggerServices, this, _targetIdFactory++);
-                _contextService.SetCurrentTarget(_target);
-                _targetWrapper = new TargetWrapper(_contextService.Services);
-                _targetWrapper.ServiceWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(_symbolService, _target.Services.GetService<IMemoryService>()));
+                var target = new TargetFromDebuggerServices(DebuggerServices, this, _targetIdFactory++);
+                _contextService.SetCurrentTarget(target);
+                _target = target;
             }
             catch (Exception ex)
             {
@@ -317,10 +324,8 @@ namespace SOS.Extensions
             Trace.TraceInformation("HostServices.DestroyTarget #{0}", _target != null ? _target.Id : "<none>");
             try
             {
-                if (_target != null)
-                {
-                    DestroyTarget(_target);
-                }
+                _target?.Destroy();
+                _target = null;
             }
             catch (Exception ex)
             {
@@ -355,6 +360,10 @@ namespace SOS.Extensions
                     return HResult.S_OK;
                 }
             }
+            catch (CommandNotSupportedException)
+            {
+                return HResult.E_NOTIMPL;
+            }
             catch (Exception ex)
             {
                 Trace.TraceError(ex.ToString());
@@ -373,6 +382,10 @@ namespace SOS.Extensions
                     return HResult.E_INVALIDARG;
                 }
             }
+            catch (CommandNotSupportedException)
+            {
+                return HResult.E_NOTIMPL;
+            }
             catch (Exception ex)
             {
                 Trace.TraceError(ex.ToString());
@@ -389,22 +402,24 @@ namespace SOS.Extensions
             {
                 DestroyTarget(self);
 
-                if (DebuggerServices != null)
-                {
-                    // This turns off any logging to console now that debugger services will be released and the console service will no longer work.
-                    DiagnosticLoggingService.Instance.SetConsole(consoleService: null, fileLoggingService: null);
-                    DebuggerServices.Release();
-                    DebuggerServices = null;
-                }
-
                 // Send shutdown event on exit
                 OnShutdownEvent.Fire();
 
-                // Release the host services wrapper
-                Release();
+                // Dispose of the global services which RemoteMemoryService but not host services (this)
+                _serviceContainer.DisposeServices();
+
+                // This turns off any logging to console now that debugger services will be released and the console service will no longer work.
+                DiagnosticLoggingService.Instance.SetConsole(consoleService: null, fileLoggingService: null);
+
+                // Release the debugger services instance
+                DebuggerServices?.ReleaseWithCheck();
+                DebuggerServices = null;
 
                 // Clear HostService instance
                 Instance = null;
+
+                // Release the host services wrapper
+                this.ReleaseWithCheck();
             }
             catch (Exception ex)
             {
index 299749e59c0dbb91c9510df7c43e7f1a76934718..4fb30b73e77e789f72073c3e596bfeb6be5c51ce 100644 (file)
@@ -15,7 +15,6 @@ namespace SOS.Extensions
     /// </summary>
     internal class MemoryServiceFromDebuggerServices : IMemoryService
     {
-        private readonly ITarget _target;
         private readonly DebuggerServices _debuggerServices;
 
         /// <summary>
@@ -27,7 +26,6 @@ namespace SOS.Extensions
         {
             Debug.Assert(target != null);
             Debug.Assert(debuggerServices != null);
-            _target = target;
             _debuggerServices = debuggerServices;
 
             switch (target.Architecture)
index fc1e61b1588f2f42a7560253ec41a9e8d760f7f3..04e9f807c390221b262b8cbd124bfbfa5045bdfc 100644 (file)
@@ -18,6 +18,53 @@ namespace SOS.Extensions
     /// </summary>
     internal class ModuleServiceFromDebuggerServices : ModuleService
     {
+        class FieldFromDebuggerServices : IField
+        {
+            public FieldFromDebuggerServices(IType type, string fieldName, uint offset)
+            {
+                Type = type;
+                Name = fieldName;
+                Offset = offset;
+            }
+            public IType Type { get; }
+
+            public string Name { get; }
+
+            public uint Offset { get; }
+        }
+
+        class TypeFromDebuggerServices : IType
+        {
+            private ModuleServiceFromDebuggerServices _moduleService;
+            private ulong _typeId;
+
+            public TypeFromDebuggerServices(ModuleServiceFromDebuggerServices moduleService, IModule module, ulong typeId, string typeName)
+            {
+                _moduleService = moduleService;
+                _typeId = typeId;
+                Module = module;
+                Name = typeName;
+            }
+
+            public IModule Module { get; }
+
+            public string Name { get; }
+
+            public List<IField> Fields => throw new NotImplementedException();
+
+            public bool TryGetField(string fieldName, out IField field)
+            {
+                HResult hr = _moduleService._debuggerServices.GetFieldOffset(Module.ModuleIndex, _typeId, Name, fieldName, out uint offset);
+                if (hr != HResult.S_OK)
+                {
+                    field = null;
+                    return false;
+                }
+                field = new FieldFromDebuggerServices(this, fieldName, offset);
+                return true;
+            }
+        }
+
         class ModuleFromDebuggerServices : Module, IModuleSymbols
         {
             // This is what dbgeng/IDebuggerServices returns for non-PE modules that don't have a timestamp
@@ -34,8 +81,8 @@ namespace SOS.Extensions
                 ulong imageBase,
                 ulong imageSize,
                 uint indexFileSize,
-                uint indexTimeStamp) 
-                    : base(moduleService.Target)
+                uint indexTimeStamp)
+                : base(moduleService.Services)
             {
                 _moduleService = moduleService;
                 ModuleIndex = moduleIndex;
@@ -45,22 +92,16 @@ namespace SOS.Extensions
                 IndexFileSize = indexTimeStamp == InvalidTimeStamp ? null : indexFileSize;
                 IndexTimeStamp = indexTimeStamp == InvalidTimeStamp ? null : indexTimeStamp;
 
-                ServiceProvider.AddService<IModuleSymbols>(this);
+                _serviceContainer.AddService<IModuleSymbols>(this);
             }
 
-            #region IModule
-
-            public override int ModuleIndex { get; }
-
-            public override string FileName { get; }
-
-            public override ulong ImageBase { get; }
-
-            public override ulong ImageSize { get; }
-
-            public override uint? IndexFileSize { get; }
+            public override void Dispose()
+            { 
+                _serviceContainer.RemoveService(typeof(IModuleSymbols));
+                base.Dispose();
+            }
 
-            public override uint? IndexTimeStamp { get; }
+            #region IModule
 
             public override Version GetVersionData()
             {
@@ -77,10 +118,7 @@ namespace SOS.Extensions
                     }
                     else
                     {
-                        if (_moduleService.Target.OperatingSystem != OSPlatform.Windows)
-                        {
-                            _version = GetVersionInner();
-                        }
+                        _version = GetVersionInner();
                     }
                 }
                 return _version;
@@ -93,10 +131,7 @@ namespace SOS.Extensions
                     HResult hr = _moduleService._debuggerServices.GetModuleVersionString(ModuleIndex, out _versionString);
                     if (!hr.IsOK)
                     {
-                        if (_moduleService.Target.OperatingSystem != OSPlatform.Windows && !IsPEImage)
-                        {
-                            _versionString = _moduleService.GetVersionString(this);
-                        }
+                        _versionString = GetVersionStringInner();
                     }
                 }
                 return _versionString;
@@ -126,6 +161,18 @@ namespace SOS.Extensions
                 return _moduleService._debuggerServices.GetOffsetBySymbol(ModuleIndex, name, out address).IsOK;
             }
 
+            bool IModuleSymbols.TryGetType(string typeName, out IType type)
+            {
+                HResult hr = _moduleService._debuggerServices.GetTypeId(ModuleIndex, typeName, out ulong typeId);
+                if (hr != HResult.S_OK)
+                {
+                    type = null;
+                    return false;
+                }
+                type = new TypeFromDebuggerServices(_moduleService, this, typeId, typeName);
+                return true;
+            }
+
             #endregion
 
             protected override bool TryGetSymbolAddressInner(string name, out ulong address)
@@ -138,8 +185,8 @@ namespace SOS.Extensions
 
         private readonly DebuggerServices _debuggerServices;
 
-        internal ModuleServiceFromDebuggerServices(ITarget target, IMemoryService rawMemoryService, DebuggerServices debuggerServices)
-            : base(target, rawMemoryService)
+        internal ModuleServiceFromDebuggerServices(IServiceProvider services, DebuggerServices debuggerServices)
+            : base(services)
         {
             Debug.Assert(debuggerServices != null);
             _debuggerServices = debuggerServices;
index b57f491fdea2392a878963e9a72b9a87aa8e73e0..069b81c9f6b6c3ccbeaae42d9d19b39de65ce94c 100644 (file)
@@ -19,7 +19,7 @@ namespace SOS.Extensions
     internal class TargetFromDebuggerServices : Target
     {
         /// <summary>
-        /// Create a target instance from IDataReader
+        /// Build a target instance from IDataReader
         /// </summary>
         internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost host, int id)
             : base(host, id, dumpPath: null)
@@ -72,22 +72,29 @@ namespace SOS.Extensions
             }
 
             // Add the thread, memory, and module services
-            IMemoryService rawMemoryService = new MemoryServiceFromDebuggerServices(this, debuggerServices);
-            ServiceProvider.AddServiceFactory<IModuleService>(() => new ModuleServiceFromDebuggerServices(this, rawMemoryService, debuggerServices));
-            ServiceProvider.AddServiceFactory<IThreadService>(() => new ThreadServiceFromDebuggerServices(this, debuggerServices));
-            ServiceProvider.AddServiceFactory<IMemoryService>(() => {
+            _serviceContainerFactory.AddServiceFactory<IModuleService>((services) => new ModuleServiceFromDebuggerServices(services, debuggerServices));
+            _serviceContainerFactory.AddServiceFactory<IThreadService>((services) => new ThreadServiceFromDebuggerServices(services, debuggerServices));
+            _serviceContainerFactory.AddServiceFactory<IMemoryService>((_) => {
                 Debug.Assert(Host.HostType != HostType.DotnetDump);
-                IMemoryService memoryService = rawMemoryService;
+                IMemoryService memoryService = new MemoryServiceFromDebuggerServices(this, debuggerServices);
                 if (IsDump && Host.HostType == HostType.Lldb)
                 {
+                    ServiceContainerFactory clone = _serviceContainerFactory.Clone();
+                    clone.RemoveServiceFactory<IMemoryService>();
+
+                    // lldb doesn't map managed modules into the address space
+                    memoryService = new ImageMappingMemoryService(clone.Build(), memoryService, managed: true);
+
                     // This is a special memory service that maps the managed assemblies' metadata into the address 
                     // space. The lldb debugger returns zero's (instead of failing the memory read) for missing pages
                     // in core dumps that older (< 5.0) createdumps generate so it needs this special metadata mapping 
                     // memory service. dotnet-dump needs this logic for clrstack -i (uses ICorDebug data targets).
-                    return new MetadataMappingMemoryService(this, memoryService);
+                    memoryService = new MetadataMappingMemoryService(clone.Build(), memoryService);
                 }
                 return memoryService;
             });
+
+            Finished();
         }
     }
 }
index 63dc87364efc92b8f6798890228dffceef8164ab..d26ee2a46fd2209e3605d53af9cec2219b5f2691 100644 (file)
@@ -5,6 +5,7 @@
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.DebugServices.Implementation;
 using Microsoft.Diagnostics.Runtime.Utilities;
+using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 
@@ -17,8 +18,8 @@ namespace SOS.Extensions
     {
         private readonly DebuggerServices _debuggerServices;
 
-        internal ThreadServiceFromDebuggerServices(ITarget target, DebuggerServices debuggerServices)
-            : base(target)
+        internal ThreadServiceFromDebuggerServices(IServiceProvider services, DebuggerServices debuggerServices)
+            : base(services)
         {
             Debug.Assert(debuggerServices != null);
             _debuggerServices = debuggerServices;
index b196c5cdd20241cdb914d978a3eb7a85128ca706..1fbb30dd50aca94c45db53970469bcaf0a3e7bd8 100644 (file)
@@ -74,6 +74,7 @@ namespace SOS.Hosting
         [Argument(Name = "arguments", Help = "Arguments to SOS command.")]
         public string[] Arguments { get; set; }
 
+        [ServiceImport]
         public SOSHost SOSHost { get; set; }
 
         public override void Invoke()
index 058cdb4dfb6b264faa6bbdad1ad115474c3d9dfc..8153ece03b218d4f981a96612ab437703dff5a78 100644 (file)
@@ -26,10 +26,11 @@ namespace SOS.Hosting
 
         public IntPtr ICorDebugDataTarget { get; }
 
-        public CorDebugDataTargetWrapper(IServiceProvider services)
+        public CorDebugDataTargetWrapper(IServiceProvider services, IRuntime runtime)
         {
             Debug.Assert(services != null);
-            _target = services.GetService<ITarget>();
+            Debug.Assert(runtime != null);
+            _target = runtime.Target;
             _symbolService = services.GetService<ISymbolService>();
             _memoryService = services.GetService<IMemoryService>();
             _threadService = services.GetService<IThreadService>();
index a5a9d4992e031fcb09d80223382a52f22169724d..86cbafb5da41ffad9c5054d957fcc6b0a4ddd1cd 100644 (file)
@@ -23,8 +23,8 @@ namespace SOS.Hosting
         // For ClrMD's magic hand shake
         private const ulong MagicCallbackConstant = 0x43;
 
-        private readonly IServiceProvider _services;
-        private readonly ulong _runtimeBaseAddress;
+        private readonly IRuntime _runtime;
+        private readonly IContextService _contextService;
         private readonly ISymbolService _symbolService;
         private readonly IMemoryService _memoryService;
         private readonly IThreadService _threadService;
@@ -39,8 +39,8 @@ namespace SOS.Hosting
         {
             Debug.Assert(services != null);
             Debug.Assert(runtime != null);
-            _services = services;
-            _runtimeBaseAddress = runtime.RuntimeModule.ImageBase;
+            _runtime = runtime;
+            _contextService = services.GetService<IContextService>();
             _symbolService = services.GetService<ISymbolService>();
             _memoryService = services.GetService<IMemoryService>();
             _threadService = services.GetService<IThreadService>();
@@ -105,11 +105,8 @@ namespace SOS.Hosting
             IntPtr self,
             out IMAGE_FILE_MACHINE machineType)
         {
-            ITarget target = _services.GetService<ITarget>();
-            if (target == null) {
-                machineType = IMAGE_FILE_MACHINE.UNKNOWN;
-                return HResult.E_FAIL;
-            }
+            ITarget target = _runtime.Target;
+            Debug.Assert(target != null);
             machineType = target.Architecture switch
             {
                 Architecture.X64 => IMAGE_FILE_MACHINE.AMD64,
@@ -206,7 +203,7 @@ namespace SOS.Hosting
             IntPtr self,
             out uint threadId)
         {
-            uint? id = _services.GetService<IThread>()?.ThreadId;
+            uint? id = _contextService.GetCurrentThread()?.ThreadId;
             if (id.HasValue)
             {
                 threadId = id.Value;
@@ -352,7 +349,7 @@ namespace SOS.Hosting
             IntPtr self,
             out ulong address)
         {
-            address = _runtimeBaseAddress;
+            address = _runtime.RuntimeModule.ImageBase;
             return HResult.S_OK;
         }
 
index 5b9a2c17de85f0653d6f092d01bec8edcbd2cc41..3058a01b61c9d1b4776b8ca816994334f73ddea0 100644 (file)
@@ -5,6 +5,7 @@
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Runtime.Utilities;
 using System;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 
 namespace SOS.Hosting
@@ -14,16 +15,14 @@ namespace SOS.Hosting
         private static readonly Guid IID_IHost = new Guid("E0CD8534-A88B-40D7-91BA-1B4C925761E9");
 
         private readonly IHost _host;
-        private readonly Func<TargetWrapper> _getTarget;
 
         public ServiceWrapper ServiceWrapper { get; } = new ServiceWrapper();
 
         public IntPtr IHost { get; }
 
-        public HostWrapper(IHost host, Func<TargetWrapper> getTarget)
+        public HostWrapper(IHost host)
         {
             _host = host;
-            _getTarget = getTarget;
 
             VTableBuilder builder = AddInterface(IID_IHost, validate: false);
             builder.AddMethod(new GetHostTypeDelegate(GetHostType));
@@ -34,7 +33,11 @@ namespace SOS.Hosting
             AddRef();
         }
 
-        protected override void Destroy() => ServiceWrapper.Dispose();
+        protected override void Destroy()
+        {
+            Trace.TraceInformation("HostWrapper.Destroy");
+            ServiceWrapper.Dispose();
+        }
 
         #region IHost
 
@@ -50,7 +53,9 @@ namespace SOS.Hosting
         /// <returns>S_OK</returns>
         private int GetCurrentTarget(IntPtr self, out IntPtr targetWrapper)
         {
-            TargetWrapper wrapper = _getTarget();
+            IContextService contextService = _host.Services.GetService<IContextService>();
+            ITarget target = contextService.GetCurrentTarget();
+            TargetWrapper wrapper = target?.Services.GetService<TargetWrapper>();
             if (wrapper == null)
             {
                 targetWrapper = IntPtr.Zero;
index d10050fc5aee3af1f2c53351df7fb3841fabc51e..9a57b03957c12af84354a87a00cbde418bfeebe1 100644 (file)
@@ -97,7 +97,7 @@ namespace SOS.Hosting
         string GetCoreClrDirectory(
             IntPtr self)
         {
-            IRuntime currentRuntime = _soshost.Services.GetService<IRuntime>();
+            IRuntime currentRuntime = _soshost.ContextService.GetCurrentRuntime();
             if (currentRuntime is not null) {
                 return Path.GetDirectoryName(currentRuntime.RuntimeModule.FileName);
             }
index 4306f83d433267a7b26471e4bdbcafb384ece36c..8e28b3c395855350cc670bd424c073f67ff86719 100644 (file)
@@ -14,7 +14,8 @@ using System.Text;
 
 namespace SOS.Hosting
 {
-    public sealed unsafe class RuntimeWrapper : COMCallableIUnknown
+    [ServiceExport(Scope = ServiceScope.Runtime)]
+    public sealed unsafe class RuntimeWrapper : COMCallableIUnknown, IDisposable
     {
         /// <summary>
         /// The runtime OS and type. Must match IRuntime::RuntimeConfiguration in runtime.h.
@@ -115,6 +116,12 @@ namespace SOS.Hosting
             AddRef();
         }
 
+        void IDisposable.Dispose()
+        {
+            Trace.TraceInformation("RuntimeWrapper.Dispose");
+            this.ReleaseWithCheck();
+        }
+
         protected override void Destroy()
         {
             Trace.TraceInformation("RuntimeWrapper.Destroy");
@@ -137,13 +144,13 @@ namespace SOS.Hosting
             // TODO: there is a better way to flush _corDebugProcess with ICorDebugProcess4::ProcessStateChanged(FLUSH_ALL)
             if (_corDebugProcess != IntPtr.Zero)
             {
-                COMHelper.Release(_corDebugProcess);
+                ComWrapper.ReleaseWithCheck(_corDebugProcess);
                 _corDebugProcess = IntPtr.Zero;
             }
             // TODO: there is a better way to flush _clrDataProcess with ICLRDataProcess::Flush()
             if (_clrDataProcess != IntPtr.Zero)
             {
-                COMHelper.Release(_clrDataProcess);
+                ComWrapper.ReleaseWithCheck(_clrDataProcess);
                 _clrDataProcess = IntPtr.Zero;
             }
         }
@@ -209,8 +216,16 @@ namespace SOS.Hosting
             if (ppClrDataProcess == null) {
                 return HResult.E_INVALIDARG;
             }
-            if (_clrDataProcess == IntPtr.Zero) {
-                _clrDataProcess = CreateClrDataProcess();
+            if (_clrDataProcess == IntPtr.Zero) 
+            {
+                try
+                {
+                    _clrDataProcess = CreateClrDataProcess();
+                }
+                catch (Exception)
+                {
+                    return HResult.E_NOINTERFACE;
+                }
             }
             *ppClrDataProcess = _clrDataProcess;
             if (*ppClrDataProcess == IntPtr.Zero) {
@@ -317,7 +332,7 @@ namespace SOS.Hosting
             }
             finally
             {
-                dataTarget.Release();
+                dataTarget.ReleaseWithCheck();
             }
         }
 
@@ -350,7 +365,7 @@ namespace SOS.Hosting
                 Build = 0,
                 Revision = 0,
             };
-            var dataTarget = new CorDebugDataTargetWrapper(_services);
+            var dataTarget = new CorDebugDataTargetWrapper(_services, _runtime);
             ulong clrInstanceId = _runtime.RuntimeModule.ImageBase;
             int hresult = 0;
             try
@@ -446,7 +461,7 @@ namespace SOS.Hosting
             }
             finally
             {
-                dataTarget.Release();
+                dataTarget.ReleaseWithCheck();
             }
         }
 
index 18cc0ae3870c2c869dfcc7ae7b05a5e98c7a7b7c..b290afdb89fd27c750305f8b68defef206bc4df6 100644 (file)
@@ -19,42 +19,45 @@ namespace SOS.Hosting
     /// <summary>
     /// Helper code to hosting the native SOS code
     /// </summary>
+    [ServiceExport(Scope = ServiceScope.Target)]
     public sealed class SOSHost : IDisposable
     {
         // This is what dbgeng/IDebuggerServices returns for non-PE modules that don't have a timestamp
         internal const uint InvalidTimeStamp = 0xFFFFFFFE;
         internal const uint InvalidChecksum = 0xFFFFFFFF;
 
-        internal readonly IServiceProvider Services;
         internal readonly ITarget Target;
-        internal readonly TargetWrapper TargetWrapper;
+        internal readonly IMemoryService MemoryService;
+
+#pragma warning disable CS0649
+        [ServiceImport]
         internal readonly IConsoleService ConsoleService;
+
+        [ServiceImport]
+        internal readonly IContextService ContextService;
+
+        [ServiceImport]
         internal readonly IModuleService ModuleService;
+
+        [ServiceImport]
         internal readonly IThreadService ThreadService;
-        internal readonly IMemoryService MemoryService;
+
+        [ServiceImport]
         private readonly SOSLibrary _sosLibrary;
+#pragma warning restore
+
         private readonly IntPtr _interface;
         private readonly ulong _ignoreAddressBitsMask;
         private bool _disposed;
 
         /// <summary>
-        /// Create an instance of the hosting class. Has the lifetime of the target. Depends on the
-        /// context service for the current thread and runtime.
+        /// Create an instance of the hosting class. Has the lifetime of the target.
         /// </summary>
-        /// <param name="services">service provider</param>
-        public SOSHost(IServiceProvider services)
-        {
-            Services = services;
-            Target = services.GetService<ITarget>() ?? throw new DiagnosticsException("No target");
-            TargetWrapper = new TargetWrapper(services);
-            Target.DisposeOnDestroy(this);
-            ConsoleService = services.GetService<IConsoleService>();
-            ModuleService = services.GetService<IModuleService>();
-            ThreadService = services.GetService<IThreadService>();
-            MemoryService = services.GetService<IMemoryService>();
-            TargetWrapper.ServiceWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(services.GetService<ISymbolService>(), MemoryService));
-            _ignoreAddressBitsMask = MemoryService.SignExtensionMask();
-            _sosLibrary = services.GetService<SOSLibrary>();
+        public SOSHost(ITarget target, IMemoryService memoryService)
+        {
+            Target = target ?? throw new DiagnosticsException("No target");
+            MemoryService = memoryService;
+            _ignoreAddressBitsMask = memoryService.SignExtensionMask();
 
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
@@ -74,8 +77,7 @@ namespace SOS.Hosting
             if (!_disposed)
             {
                 _disposed = true;
-                TargetWrapper.Release();
-                COMHelper.Release(_interface);
+                ComWrapper.ReleaseWithCheck(_interface);
             }
         }
 
@@ -577,7 +579,7 @@ namespace SOS.Hosting
             IntPtr context,
             uint contextSize)
         {
-            IThread thread = Services.GetService<IThread>();
+            IThread thread = ContextService.GetCurrentThread();
             if (thread is not null)
             {
                 return GetThreadContextBySystemId(self, thread.ThreadId, 0, contextSize, context);
@@ -654,7 +656,7 @@ namespace SOS.Hosting
             IntPtr self,
             out uint id)
         {
-            IThread thread = Services.GetService<IThread>();
+            IThread thread = ContextService.GetCurrentThread();
             if (thread is not null) { 
                 return GetThreadIdBySystemId(self, thread.ThreadId, out id);
             }
@@ -668,11 +670,7 @@ namespace SOS.Hosting
         {
             try
             {
-                var contextService = Services.GetService<IContextService>();
-                if (contextService is null) {
-                    return HResult.E_FAIL;
-                }
-                contextService.SetCurrentThread(ThreadService.GetThreadFromIndex(unchecked((int)id)).ThreadId);
+                ContextService.SetCurrentThread(ThreadService.GetThreadFromIndex(unchecked((int)id)).ThreadId);
             }
             catch (DiagnosticsException)
             {
@@ -685,7 +683,7 @@ namespace SOS.Hosting
             IntPtr self,
             out uint sysId)
         {
-            IThread thread = Services.GetService<IThread>();
+            IThread thread = ContextService.GetCurrentThread();
             if (thread is not null)
             { 
                 sysId = thread.ThreadId;
@@ -749,7 +747,7 @@ namespace SOS.Hosting
             IntPtr self,
             ulong* offset)
         {
-            IThread thread = Services.GetService<IThread>();
+            IThread thread = ContextService.GetCurrentThread();
             if (thread is not null)
             { 
                 try
@@ -848,7 +846,7 @@ namespace SOS.Hosting
             int index, 
             out ulong value)
         {
-            IThread thread = Services.GetService<IThread>();
+            IThread thread = ContextService.GetCurrentThread();
             if (thread is not null)
             { 
                 if (thread.TryGetRegisterValue(index, out value))
@@ -895,4 +893,37 @@ namespace SOS.Hosting
             }
         }
     }
+
+    public static class ComWrapper
+    {
+        /// <summary>
+        /// Asserts the the reference count doesn't go below 0.
+        /// </summary>
+        /// <param name="comCallable">wrapper instance</param>
+        public static void ReleaseWithCheck(this COMCallableIUnknown comCallable)
+        {
+            int count = comCallable.Release();
+            Debug.Assert(count >= 0);
+        }
+
+        /// <summary>
+        /// Asserts the the reference count doesn't go below 0.
+        /// </summary>
+        /// <param name="callableCOM">wrapper instance</param>
+        public static void ReleaseWithCheck(this CallableCOMWrapper callableCOM)
+        {
+            int count = callableCOM.Release();
+            Debug.Assert(count >= 0);
+        }
+
+        /// <summary>
+        /// Asserts the the reference count doesn't go below 0.
+        /// </summary>
+        /// <param name="callableCOM">wrapper instance</param>
+        public static void ReleaseWithCheck(IntPtr punk)
+        {
+            int count = COMHelper.Release(punk);
+            Debug.Assert(count >= 0);
+        }
+    }
 }
index a43683f7c37c3dc1fe7619203eeb8a9436a62552..d96bde6a5d8dabf5f87399a95ac6a2cd03f4ee96 100644 (file)
@@ -15,7 +15,7 @@ namespace SOS.Hosting
     /// <summary>
     /// Helper code to load and initialize SOS
     /// </summary>
-    public sealed class SOSLibrary
+    public sealed class SOSLibrary : IDisposable
     {
         [UnmanagedFunctionPointer(CallingConvention.Winapi)]
         private delegate int SOSCommandDelegate(
@@ -33,7 +33,6 @@ namespace SOS.Hosting
         private const string SOSInitialize = "SOSInitializeByHost";
         private const string SOSUninitialize = "SOSUninitializeByHost";
 
-        private readonly IContextService _contextService;
         private readonly HostWrapper _hostWrapper;
         private IntPtr _sosLibrary = IntPtr.Zero;
 
@@ -42,6 +41,7 @@ namespace SOS.Hosting
         /// </summary>
         public string SOSPath { get; set; }
 
+        [ServiceExport(Scope = ServiceScope.Global)]
         public static SOSLibrary Create(IHost host)
         {
             SOSLibrary sosLibrary = null;
@@ -53,13 +53,8 @@ namespace SOS.Hosting
             catch
             {
                 sosLibrary.Uninitialize();
-                sosLibrary = null;
                 throw;
             }
-            host.OnShutdownEvent.Register(() => {
-                sosLibrary.Uninitialize();
-                sosLibrary = null;
-            });
             return sosLibrary;
         }
 
@@ -69,14 +64,13 @@ namespace SOS.Hosting
         /// <param name="target">target instance</param>
         private SOSLibrary(IHost host)
         {
-            _contextService = host.Services.GetService<IContextService>();
-
             string rid = InstallHelper.GetRid();
             SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
-
-            _hostWrapper = new HostWrapper(host, () => GetSOSHost()?.TargetWrapper);
+            _hostWrapper = new HostWrapper(host);
         }
 
+        void IDisposable.Dispose() => Uninitialize();
+
         /// <summary>
         /// Loads and initializes the SOS module.
         /// </summary>
@@ -143,7 +137,7 @@ namespace SOS.Hosting
                 Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.FreeLibrary(_sosLibrary);
                 _sosLibrary = IntPtr.Zero;
             }
-            _hostWrapper.Release();
+            _hostWrapper.ReleaseWithCheck();
         }
 
         /// <summary>
@@ -162,12 +156,14 @@ namespace SOS.Hosting
                 throw new DiagnosticsException($"SOS command not found: {command}");
             }
             int result = commandFunc(client, arguments ?? "");
+            if (result == HResult.E_NOTIMPL)
+            {
+                throw new CommandNotSupportedException($"SOS command not found: {command}");
+            }
             if (result != HResult.S_OK)
             {
                 Trace.TraceError($"SOS command FAILED 0x{result:X8}");
             }
         }
-
-        private SOSHost GetSOSHost() => _contextService.Services.GetService<SOSHost>();
     }
 }
index 43ac85e9a6b74a480ac9f79197959ed23a5a9997..517459b6cb339217739715c4a49d18a0325f7219 100644 (file)
@@ -23,7 +23,7 @@ namespace SOS.Hosting
             Trace.TraceInformation("ServiceWrapper.Dispose");
             foreach (var wrapper in _wrappers.Values)
             {
-                wrapper.Release();
+                wrapper.ReleaseWithCheck();
             }
             _wrappers.Clear();
         }
@@ -95,7 +95,6 @@ namespace SOS.Hosting
             if (wrapper == null) {
                 return HResult.E_NOINTERFACE;
             }
-            wrapper.AddRef();
             return COMHelper.QueryInterface(wrapper.IUnknownObject, guid, out ptr);
         }
     }
index ca1292ae108dc0d3a4611e8421b0c3733ceadd21..86899321bc6460721e43aa3772ebf15767a39353 100644 (file)
@@ -62,7 +62,6 @@ namespace SOS.Hosting
             _symbolService = symbolService;
             _memoryService = memoryService;
             _ignoreAddressBitsMask = memoryService.SignExtensionMask();
-            Debug.Assert(_symbolService != null);
 
             VTableBuilder builder = AddInterface(IID_ISymbolService, validate: false);
             builder.AddMethod(new ParseSymbolPathDelegate(ParseSymbolPath));
index 6a5531814f8e663d055e9a13b06b0d8caee54ad0..f96663b0aeb84246c716c44bce27fa5f2cf08c27 100644 (file)
@@ -5,13 +5,13 @@
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Runtime.Utilities;
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 
 namespace SOS.Hosting
 {
-    public sealed unsafe class TargetWrapper : COMCallableIUnknown
+    [ServiceExport(Scope = ServiceScope.Target)]
+    public sealed unsafe class TargetWrapper : COMCallableIUnknown, IDisposable
     {
         // Must be the same as ITarget::OperatingSystem
         enum OperatingSystem
@@ -28,14 +28,18 @@ namespace SOS.Hosting
 
         public IntPtr ITarget { get; }
 
-        private readonly IServiceProvider _services;
         private readonly ITarget _target;
-        private readonly Dictionary<IRuntime, RuntimeWrapper> _wrappers = new Dictionary<IRuntime, RuntimeWrapper>();
+        private readonly IContextService _contextService;
 
-        public TargetWrapper(IServiceProvider services)
+        public TargetWrapper(ITarget target, IContextService contextService, ISymbolService symbolService, IMemoryService memoryService)
         {
-            _services = services;
-            _target = services.GetService<ITarget>() ?? throw new DiagnosticsException("No target");
+            Debug.Assert(target != null);
+            Debug.Assert(contextService != null);
+            Debug.Assert(symbolService != null);
+            _target = target;
+            _contextService = contextService;
+
+            ServiceWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(symbolService, memoryService));
 
             VTableBuilder builder = AddInterface(IID_ITarget, validate: false);
 
@@ -50,15 +54,17 @@ namespace SOS.Hosting
             AddRef();
         }
 
+        void IDisposable.Dispose()
+        {
+            Trace.TraceInformation("TargetWrapper.Dispose");
+            this.ReleaseWithCheck();
+        }
+
         protected override void Destroy()
         {
             Trace.TraceInformation("TargetWrapper.Destroy");
+            ServiceWrapper.RemoveServiceWrapper(SymbolServiceWrapper.IID_ISymbolService);
             ServiceWrapper.Dispose();
-            foreach (RuntimeWrapper wrapper in _wrappers.Values)
-            {
-                wrapper.Release();
-            }
-            _wrappers.Clear();
         }
 
         private OperatingSystem GetOperatingSystem(
@@ -89,14 +95,13 @@ namespace SOS.Hosting
             if (ppRuntime == null) {
                 return HResult.E_INVALIDARG;
             }
-            IRuntime runtime = _services.GetService<IRuntime>();
-            if (runtime == null) {
+            IRuntime runtime = _contextService.GetCurrentRuntime();
+            if (runtime is null) {
                 return HResult.E_NOINTERFACE;
             }
-            if (!_wrappers.TryGetValue(runtime, out RuntimeWrapper wrapper))
-            {
-                wrapper = new RuntimeWrapper(_services, runtime);
-                _wrappers.Add(runtime, wrapper);
+            RuntimeWrapper wrapper = runtime.Services.GetService<RuntimeWrapper>();
+            if (wrapper is null) {
+                return HResult.E_NOINTERFACE;
             }
             *ppRuntime = wrapper.IRuntime;
             return HResult.S_OK;
index 90d7ca3fb0c694eae447451301a8e877540812f5..7bc2e2c49d48863e6c05ae2a37bdede480881569 100644 (file)
@@ -62,7 +62,7 @@ VERIFY:\s*Total\s+<DECVAL>\s+objects\s+
 #
 
 SOSCOMMAND:SOSStatus
-VERIFY:.*\.NET Core .*runtime at.*\s+
+VERIFY:.*\.NET Core .*runtime.*at.*\s+
 
 IFDEF:TRIAGE_DUMP
 SOSCOMMAND:setclrpath %DESKTOP_RUNTIME_PATH%
index a05ce1cfefb427d503810c2f01a7b084a4b839ac..d0ea28430a50f8a2da4ce2ce6959fbce95ec387a 100644 (file)
@@ -74,10 +74,10 @@ VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+SymbolTestApp\.Program\.Main\(.*\)\s+\[(?i:.*[\\
 
 SOSCOMMAND:runtimes
 !IFDEF:DESKTOP
-VERIFY:.*\.NET Core .*runtime at.*\s+
+VERIFY:.*\.NET Core .*runtime.*at.*\s+
 ENDIF:DESKTOP
 IFDEF:DESKTOP
-VERIFY:.*\Desktop .NET Framework.*runtime at.*\s+
+VERIFY:.*\Desktop .NET Framework.*runtime.*at.*\s+
 ENDIF:DESKTOP
 
 SOSCOMMAND:SOSStatus
index f874000131a5d99243b6c96028c8052bf75fa6a7..540e1cc9f59fde61010ce3f15519b3da4bd6e4a4 100644 (file)
@@ -439,6 +439,44 @@ DbgEngServices::GetOffsetBySymbol(
     return m_symbols->GetOffsetByName(symbolName.c_str(), offset);
 }
 
+HRESULT
+DbgEngServices::GetTypeId(
+    ULONG moduleIndex,
+    PCSTR typeName,
+    PULONG64 typeId)
+{
+    ULONG64 moduleBase;
+    HRESULT hr = m_symbols->GetModuleByIndex(moduleIndex, &moduleBase);
+    if (FAILED(hr)) {
+        return hr;
+    }
+
+    ULONG typeIdTemp = 0;
+    hr = m_symbols->GetTypeId(moduleBase, typeName, &typeIdTemp);
+    // typeId is 64 bit for compatibility across platforms, can't pass it in to GetTypeId
+    // that expects a 32 bit ULONG.
+    *typeId = typeIdTemp;
+
+    return hr;
+}
+
+HRESULT 
+DbgEngServices::GetFieldOffset(
+    ULONG moduleIndex,
+    PCSTR typeName, // Unused on windbg
+    ULONG64 typeId,
+    PCSTR fieldName,
+    PULONG offset)
+{
+    ULONG64 moduleBase;
+    HRESULT hr = m_symbols->GetModuleByIndex(moduleIndex, &moduleBase);
+    if (FAILED(hr)) {
+        return hr;
+    }
+
+    return m_symbols->GetFieldOffset(moduleBase, (ULONG)typeId, fieldName, offset);
+}
+
 ULONG
 DbgEngServices::GetOutputWidth()
 {
index 377025d49a7b0930c8b4ec45c7d8f0455be22e78..e51a3604ab26e376ccbb694b05a16baf67483f61 100644 (file)
@@ -182,6 +182,18 @@ public:
         ULONG moduleIndex,
         PCSTR name,
         PULONG64 offset);
+        
+    HRESULT STDMETHODCALLTYPE GetTypeId(
+        ULONG moduleIndex,
+        PCSTR typeName,
+        PULONG64 typeId); 
+
+    HRESULT STDMETHODCALLTYPE GetFieldOffset(
+        ULONG moduleIndex,
+        PCSTR typeName,
+        ULONG64 typeId,
+        PCSTR fieldName,
+        PULONG offset);
 
     ULONG STDMETHODCALLTYPE GetOutputWidth();
 
index e155bbb4b12aa3b78974d3b5a996ce06175d4979..a01ebcdf1041c3404692b6ddeb58cb08026b2ef7 100644 (file)
@@ -473,9 +473,7 @@ HRESULT SymbolReader::LoadSymbolsForPortablePDB(__in_z WCHAR* pModuleName, ___in
     {
         return E_NOINTERFACE;
     }
-    m_symbolReaderHandle = GetSymbolService()->LoadSymbolsForModule(
-        pModuleName, isFileLayout, peAddress, (int)peSize, inMemoryPdbAddress, (int)inMemoryPdbSize);
-
+    m_symbolReaderHandle = symbolService->LoadSymbolsForModule(pModuleName, isFileLayout, peAddress, (int)peSize, inMemoryPdbAddress, (int)inMemoryPdbSize);
     if (m_symbolReaderHandle == 0)
     {
         return E_FAIL;
index e478ab092ecd75293c4845f6e0687b287295307b..1b4abe1930ddb22c4b962c4468da2774ef582dc9 100644 (file)
@@ -133,7 +133,7 @@ ISymbolService* Extensions::GetSymbolService()
 {
     if (m_pSymbolService == nullptr)
     {
-           ITarget* target = GetTarget();
+        ITarget* target = GetTarget();
         if (target != nullptr)
         {
             target->GetService(__uuidof(ISymbolService), (void**)&m_pSymbolService);
index 66f2aab5c474cc2201f8c74d001c77847b3d81fb..74e37fab649dbc55c4c392f7a125f6438b473a49 100644 (file)
@@ -144,6 +144,18 @@ public:
         PCSTR name,
         PULONG64 offset) = 0;
 
+    virtual HRESULT STDMETHODCALLTYPE GetTypeId(
+        ULONG moduleIndex,
+        PCSTR typeName,
+        PULONG64 typeId) = 0; 
+
+    virtual HRESULT STDMETHODCALLTYPE GetFieldOffset(
+        ULONG moduleIndex,
+        PCSTR typeName,
+        ULONG64 typeId,
+        PCSTR fieldName,
+        PULONG offset) = 0;
+
     virtual ULONG STDMETHODCALLTYPE GetOutputWidth() = 0;
 
     virtual HRESULT STDMETHODCALLTYPE SupportsDml(PULONG supported) = 0;
index 61f797e5b81b5892e117e857b0172ea8116615db..31b4a4ab261c6fc39adcbd780d857f230677551f 100644 (file)
@@ -950,16 +950,16 @@ LLDBServices::GetNameByOffset(
         goto exit;
     }
 
-    address = target.ResolveLoadAddress(offset);
-    if (!address.IsValid())
-    {
-        hr = E_INVALIDARG;
-        goto exit;
-    }
-
     // If module index is invalid, add module name to symbol
     if (moduleIndex == DEBUG_ANY_ID)
     {
+        address = target.ResolveLoadAddress(offset);
+        if (!address.IsValid())
+        {
+            hr = E_INVALIDARG;
+            goto exit;
+        }
+
         module = address.GetModule();
         if (!module.IsValid())
         {
@@ -982,6 +982,13 @@ LLDBServices::GetNameByOffset(
             goto exit;
         }
 
+        address = module.ResolveFileAddress(offset);
+        if (!address.IsValid())
+        {
+            hr = E_INVALIDARG;
+            goto exit;
+        }
+
         if (module != address.GetModule())
         {
             hr = E_INVALIDARG;
@@ -2242,6 +2249,7 @@ LLDBServices::OutputString(
         file = m_debugger.GetOutputFileHandle();
     }
     fputs(str, file);
+    fflush(file);
 }
 
 HRESULT 
@@ -2390,6 +2398,131 @@ exit:
     return hr;
 }
 
+HRESULT
+LLDBServices::GetTypeId(
+    ULONG moduleIndex,
+    PCSTR typeName,
+    PULONG64 typeId)
+{
+    HRESULT hr = S_OK;
+
+    lldb::SBTarget target;
+    lldb::SBModule module;
+    lldb::SBTypeList typeList;
+    lldb::SBType type;
+
+    if (typeId == nullptr)
+    {
+        hr = E_INVALIDARG;
+        goto exit;
+    }
+
+    *typeId = -1;
+
+    target = m_debugger.GetSelectedTarget();
+    if (!target.IsValid())
+    {
+        hr = E_FAIL;
+        goto exit;
+    }
+
+    module = target.GetModuleAtIndex(moduleIndex);
+    if (!module.IsValid())
+    {
+        hr = E_INVALIDARG;
+        goto exit;
+    }
+
+    type = module.FindFirstType(typeName);
+    if (!type.IsValid())
+    {
+        hr = E_INVALIDARG;
+        goto exit;
+    }
+
+exit:
+    return hr;
+}
+
+HRESULT
+LLDBServices::GetFieldOffset(
+    ULONG moduleIndex,
+    PCSTR typeName,
+    ULONG64 typeId,     // unused on lldb
+    PCSTR fieldName,
+    PULONG offset)
+{
+    HRESULT hr = S_OK;
+
+    lldb::SBTarget target;
+    lldb::SBModule module;
+    lldb::SBTypeList typeList;
+    lldb::SBType type;
+    lldb::SBTypeMember field;
+    lldb::SBTypeMember baseClassTypeMember;
+    lldb::SBType baseClass;
+    std::vector<lldb::SBType> baseClassTypes;
+
+    if (offset == nullptr)
+    {
+        hr = E_INVALIDARG;
+        goto exit;
+    }
+
+    *offset = -1;
+
+    target = m_debugger.GetSelectedTarget();
+    if (!target.IsValid())
+    {
+        hr = E_FAIL;
+        goto exit;
+    }
+
+    module = target.GetModuleAtIndex(moduleIndex);
+    if (!module.IsValid())
+    {
+        hr = E_INVALIDARG;
+        goto exit;
+    }
+
+    type = module.FindFirstType(typeName);
+    if (!type.IsValid())
+    {
+        hr = E_INVALIDARG;
+        goto exit;
+    }
+
+    // lldb only returns the information about the specific class you requested, not any base
+    // classes. So we have to do a DFS to find the field we care about.
+    baseClassTypes.push_back(type);
+    while (baseClassTypes.size() > 0)
+    {
+        type = baseClassTypes.back();
+        baseClassTypes.pop_back();
+
+        for (int fieldIndex = 0; fieldIndex < type.GetNumberOfFields(); ++fieldIndex)
+        {
+            field = type.GetFieldAtIndex(fieldIndex);
+            if (strcmp(fieldName, field.GetName()) == 0)
+            {
+                *offset = field.GetOffsetInBytes();
+                goto exit;
+            }
+        }
+
+        for (int baseClassIndex = 0; baseClassIndex < type.GetNumberOfDirectBaseClasses(); ++baseClassIndex)
+        {
+            baseClass = type.GetDirectBaseClassAtIndex(baseClassIndex).GetType();
+            baseClassTypes.push_back(baseClass);
+        }
+    }
+
+    hr = E_INVALIDARG;
+
+exit:
+    return hr;
+}
+
 ULONG
 LLDBServices::GetOutputWidth()
 {
index d741ed51eb10a41f5a2b9b8af7ea3d9f229d068c..18d580d7df9049d91d4d7eb635099705ed02f018 100644 (file)
@@ -395,6 +395,18 @@ public:
         ULONG moduleIndex,
         PCSTR name,
         PULONG64 offset);
+    
+    HRESULT STDMETHODCALLTYPE GetTypeId(
+        ULONG moduleIndex,
+        PCSTR typeName,
+        PULONG64 typeId); 
+
+    HRESULT STDMETHODCALLTYPE GetFieldOffset(
+        ULONG moduleIndex,
+        PCSTR typeName,
+        ULONG64 typeId,
+        PCSTR fieldName,
+        PULONG offset);
 
     ULONG STDMETHODCALLTYPE GetOutputWidth();
 
index 6071b5ac287f281dbc6b8b94bbfd08de34e73b0d..d155aff69a35a91bbf3f220f73ad1bfe939935aa 100644 (file)
@@ -22,47 +22,33 @@ namespace Microsoft.Diagnostics.Tools.Dump
 {
     public class Analyzer : IHost
     {
-        private readonly ServiceProvider _serviceProvider;
+        private readonly ServiceManager _serviceManager;
         private readonly ConsoleService _consoleService;
         private readonly FileLoggingConsoleService _fileLoggingConsoleService;
         private readonly CommandService _commandService;
-        private readonly SymbolService _symbolService;
-        private readonly ContextService _contextService;
+        private readonly List<ITarget> _targets = new();
+        private ServiceContainer _serviceContainer;
         private int _targetIdFactory;
-        private Target _target;
 
         public Analyzer()
         {
             DiagnosticLoggingService.Initialize();
 
-            _serviceProvider = new ServiceProvider();
+            _serviceManager = new ServiceManager();
             _consoleService = new ConsoleService();
-            _fileLoggingConsoleService  = new FileLoggingConsoleService(_consoleService);
-            _commandService = new CommandService();
-            _symbolService = new SymbolService(this);
-            _contextService = new ContextService(this);
+            _fileLoggingConsoleService = new FileLoggingConsoleService(_consoleService);
             DiagnosticLoggingService.Instance.SetConsole(_fileLoggingConsoleService, _fileLoggingConsoleService);
 
-            _serviceProvider.AddService<IHost>(this);
-            _serviceProvider.AddService<IConsoleService>(_fileLoggingConsoleService);
-            _serviceProvider.AddService<IConsoleFileLoggingService>(_fileLoggingConsoleService);
-            _serviceProvider.AddService<IDiagnosticLoggingService>(DiagnosticLoggingService.Instance);
-            _serviceProvider.AddService<ICommandService>(_commandService);
-            _serviceProvider.AddService<ISymbolService>(_symbolService);
-            _serviceProvider.AddService<IContextService>(_contextService);
-            _serviceProvider.AddServiceFactory<SOSLibrary>(() => SOSLibrary.Create(this));
-
-            _contextService.ServiceProvider.AddServiceFactory<ClrMDHelper>(() => {
-                ClrRuntime clrRuntime = _contextService.Services.GetService<ClrRuntime>();
-                return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null;
+            _commandService = new CommandService();
+            _serviceManager.NotifyExtensionLoad.Register(_commandService.AddCommands);
+
+            // Add and remove targets from the host
+            OnTargetCreate.Register((target) => {
+                _targets.Add(target);
+                target.OnDestroyEvent.Register(() => {
+                    _targets.Remove(target);
+                });
             });
-
-            _commandService.AddCommands(new Assembly[] { typeof(Analyzer).Assembly });
-            _commandService.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly });
-            _commandService.AddCommands(new Assembly[] { typeof(SOSHost).Assembly });
-            _commandService.AddCommands(typeof(HelpCommand), (services) => new HelpCommand(_commandService, services));
-            _commandService.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleService.Stop));
-            _commandService.AddCommands(typeof(SOSCommand), (services) => new SOSCommand(_commandService, services));
         }
 
         public Task<int> Analyze(FileInfo dump_path, string[] command)
@@ -70,49 +56,89 @@ namespace Microsoft.Diagnostics.Tools.Dump
             _fileLoggingConsoleService.WriteLine($"Loading core dump: {dump_path} ...");
 
             // Attempt to load the persisted command history
-            string dotnetHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".dotnet");
-            string historyFileName = Path.Combine(dotnetHome, "dotnet-dump.history");
+            string historyFileName = null;
             try
             {
+                historyFileName = Path.Combine(Utilities.GetDotNetHomeDirectory(), "dotnet-dump.history");
                 string[] history = File.ReadAllLines(historyFileName);
                 _consoleService.AddCommandHistory(history);
             }
             catch (Exception ex) when
                 (ex is IOException ||
+                 ex is ArgumentNullException ||
                  ex is UnauthorizedAccessException ||
                  ex is NotSupportedException ||
                  ex is SecurityException)
             {
             }
 
+            // Register all the services and commands in the Microsoft.Diagnostics.DebugServices.Implementation assembly
+            _serviceManager.RegisterAssembly(typeof(Target).Assembly);
+
+            // Register all the services and commands in the dotnet-dump (this) assembly
+            _serviceManager.RegisterAssembly(typeof(Analyzer).Assembly);
+
+            // Register all the services and commands in the SOS.Hosting assembly
+            _serviceManager.RegisterAssembly(typeof(SOSHost).Assembly);
+
+            // Register all the services and commands in the Microsoft.Diagnostics.ExtensionCommands assembly
+            _serviceManager.RegisterAssembly(typeof(ClrMDHelper).Assembly);
+
+            // Add the specially handled exit command
+            _commandService.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleService.Stop));
+
+            // Add "sos" command manually
+            _commandService.AddCommands(typeof(SOSCommand), (services) => new SOSCommand(_commandService, services));
+
+            // Display any extension assembly loads on console
+            _serviceManager.NotifyExtensionLoad.Register((Assembly assembly) => _fileLoggingConsoleService.WriteLine($"Loading extension {assembly.Location}"));
+
             // Load any extra extensions
-            LoadExtensions();
+            _serviceManager.LoadExtensions();
+
+            // Loading extensions or adding service factories not allowed after this point.
+            _serviceManager.FinalizeServices();
+
+            // Add all the global services to the global service container
+            _serviceContainer = _serviceManager.CreateServiceContainer(ServiceScope.Global, parent: null);
+            _serviceContainer.AddService<IServiceManager>(_serviceManager);
+            _serviceContainer.AddService<IHost>(this);
+            _serviceContainer.AddService<IConsoleService>(_fileLoggingConsoleService);
+            _serviceContainer.AddService<IConsoleFileLoggingService>(_fileLoggingConsoleService);
+            _serviceContainer.AddService<IDiagnosticLoggingService>(DiagnosticLoggingService.Instance);
+            _serviceContainer.AddService<ICommandService>(_commandService);
+
+            var symbolService = new SymbolService(this);
+            _serviceContainer.AddService<ISymbolService>(symbolService);
+
+            var contextService = new ContextService(this);
+            _serviceContainer.AddService<IContextService>(contextService);
 
             try
             {
                 using DataTarget dataTarget = DataTarget.LoadDump(dump_path.FullName);
 
                 OSPlatform targetPlatform = dataTarget.DataReader.TargetPlatform;
-                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || dataTarget.DataReader.EnumerateModules().Any((module) => Path.GetExtension(module.FileName) == ".dylib"))
+                if (targetPlatform != OSPlatform.OSX &&
+                    (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
+                     dataTarget.DataReader.EnumerateModules().Any((module) => Path.GetExtension(module.FileName) == ".dylib")))
                 {
                     targetPlatform = OSPlatform.OSX;
                 }
-                _target = new TargetFromDataReader(dataTarget.DataReader, targetPlatform, this, _targetIdFactory++, dump_path.FullName);
-                _contextService.SetCurrentTarget(_target);
-
-                _target.ServiceProvider.AddServiceFactory<SOSHost>(() => new SOSHost(_contextService.Services));
+                var target = new TargetFromDataReader(dataTarget.DataReader, targetPlatform, this, _targetIdFactory++, dump_path.FullName);
+                contextService.SetCurrentTarget(target);
 
                 // Automatically enable symbol server support, default cache and search for symbols in the dump directory
-                _symbolService.AddSymbolServer(msdl: true, symweb: false, retryCount: 3);
-                _symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
-                _symbolService.AddDirectoryPath(Path.GetDirectoryName(dump_path.FullName));
+                symbolService.AddSymbolServer(msdl: true, symweb: false, retryCount: 3);
+                symbolService.AddCachePath(symbolService.DefaultSymbolCache);
+                symbolService.AddDirectoryPath(Path.GetDirectoryName(dump_path.FullName));
 
                 // Run the commands from the dotnet-dump command line
                 if (command != null)
                 {
                     foreach (string cmd in command)
                     {
-                        _commandService.Execute(cmd, _contextService.Services);
+                        _commandService.Execute(cmd, contextService.Services);
                         if (_consoleService.Shutdown)
                         {
                             break;
@@ -128,7 +154,7 @@ namespace Microsoft.Diagnostics.Tools.Dump
                     _consoleService.Start((string prompt, string commandLine, CancellationToken cancellation) =>
                     {
                         _fileLoggingConsoleService.WriteLine("{0}{1}", prompt, commandLine);
-                        _commandService.Execute(commandLine, _contextService.Services);
+                        _commandService.Execute(commandLine, contextService.Services);
                     });
                 }
             }
@@ -147,24 +173,33 @@ namespace Microsoft.Diagnostics.Tools.Dump
             }
             finally
             {
-                if (_target != null)
+                foreach (ITarget target in _targets.ToArray())
                 {
-                    DestroyTarget(_target);
+                    target.Destroy();
                 }
+                _targets.Clear();
+
                 // Persist the current command history
-                try
-                {
-                    File.WriteAllLines(historyFileName, _consoleService.GetCommandHistory());
-                }
-                catch (Exception ex) when
-                    (ex is IOException ||
-                     ex is UnauthorizedAccessException ||
-                     ex is NotSupportedException ||
-                     ex is SecurityException)
+                if (historyFileName != null)
                 {
+                    try
+                    {
+                        File.WriteAllLines(historyFileName, _consoleService.GetCommandHistory());
+                    }
+                    catch (Exception ex) when
+                        (ex is IOException ||
+                         ex is UnauthorizedAccessException ||
+                         ex is NotSupportedException ||
+                         ex is SecurityException)
+                    {
+                    }
                 }
+
                 // Send shutdown event on exit
                 OnShutdownEvent.Fire();
+
+                // Dispose of the global services
+                _serviceContainer.DisposeServices();
             }
             return Task.FromResult(0);
         }
@@ -173,69 +208,14 @@ namespace Microsoft.Diagnostics.Tools.Dump
 
         public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
 
-        HostType IHost.HostType => HostType.DotnetDump;
+        public IServiceEvent<ITarget> OnTargetCreate { get; } = new ServiceEvent<ITarget>();
 
-        IServiceProvider IHost.Services => _serviceProvider;
+        public HostType HostType => HostType.DotnetDump;
 
-        IEnumerable<ITarget> IHost.EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty<ITarget>();
+        public IServiceProvider Services => _serviceContainer;
 
-        public void DestroyTarget(ITarget target)
-        {
-            if (target == null)
-            {
-                throw new ArgumentNullException(nameof(target));
-            }
-            if (target == _target)
-            {
-                _target = null;
-                _contextService.ClearCurrentTarget();
-                if (target is IDisposable disposable)
-                {
-                    disposable.Dispose();
-                }
-            }
-        }
+        public IEnumerable<ITarget> EnumerateTargets() => _targets.ToArray();
 
         #endregion
-
-        /// <summary>
-        /// Load any extra extensions in the search path
-        /// </summary>
-        /// <param name="commandService">Used to add the commands</param>
-        private void LoadExtensions()
-        {
-            string diagnosticExtensions = Environment.GetEnvironmentVariable("DOTNET_DIAGNOSTIC_EXTENSIONS");
-            if (!string.IsNullOrEmpty(diagnosticExtensions))
-            {
-                string[] paths = diagnosticExtensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
-                foreach (string extensionPath in paths)
-                {
-                    LoadExtension(extensionPath);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Load any extra extensions in the search path
-        /// </summary>
-        /// <param name="commandService">Used to add the commands</param>
-        /// <param name="extensionPath">Extension assembly path</param>
-        private void LoadExtension(string extensionPath)
-        {
-            Assembly assembly = null;
-            try
-            {
-                assembly = Assembly.LoadFrom(extensionPath);
-            }
-            catch (Exception ex) when (ex is IOException || ex is ArgumentException || ex is BadImageFormatException || ex is System.Security.SecurityException)
-            {
-                _fileLoggingConsoleService.WriteLineError($"Extension load {extensionPath} FAILED {ex.Message}");
-            }
-            if (assembly is not null)
-            {
-                _commandService.AddCommands(assembly);
-                _fileLoggingConsoleService.WriteLine($"Extension loaded {extensionPath}");
-            }
-        }
     }
 }
index 016dbfe0d5759c526b5dd458bffbc9915874b7ba..11b0a7cc1aba6514e5e2b1e05e11df11c299a327 100644 (file)
@@ -22,21 +22,17 @@ namespace Microsoft.Diagnostics.Tools.Dump
         [Argument(Name = "address", Help = "Address to dump.")]
         public string AddressValue
         {
-            get => Address?.ToString();
-            set => Address = ParseAddress(value);
+            get => _address?.ToString();
+            set => _address = ParseAddress(value);
         }
 
-        public ulong? Address { get; set; }
-
         [Option(Name = "--end", Aliases = new string[] { "-e" }, Help = "Ending address to dump.")]
         public string EndAddressValue
         {
-            get => EndAddress?.ToString();
-            set => EndAddress = ParseAddress(value);
+            get => _endAddress?.ToString();
+            set => _endAddress = ParseAddress(value);
         }
 
-        public ulong? EndAddress { get; set; }
-
         // ****************************************************************************************
         // The following arguments are static so they are remembered for the "d" command. This 
         // allows a command like "db <address>" to be followed by "d" and the next block is dumped 
@@ -71,16 +67,21 @@ namespace Microsoft.Diagnostics.Tools.Dump
         [Option(Name = "--show-address", Help = "Display the addresses of data found.")]
         public bool ShowAddress { get; set; } = true;
 
+        [ServiceImport]
         public IMemoryService MemoryService { get; set; }
 
+        [ServiceImport]
         public ITarget Target { get; set; }
 
+        private ulong? _address;
+        private ulong? _endAddress;
+
         private static ulong _lastAddress;
 
         public override void Invoke()
         {
-            if (Address.HasValue) {
-                _lastAddress = Address.Value;
+            if (_address.HasValue) {
+                _lastAddress = _address.Value;
             }
 
             int length = Length;
@@ -100,11 +101,11 @@ namespace Microsoft.Diagnostics.Tools.Dump
             }
             Length = length;
 
-            if (EndAddress.HasValue) {
-                if (EndAddress.Value <= _lastAddress) {
+            if (_endAddress.HasValue) {
+                if (_endAddress.Value <= _lastAddress) {
                     throw new ArgumentException("Cannot dump a negative range");
                 }
-                int range = (int)(EndAddress.Value - _lastAddress);
+                int range = (int)(_endAddress.Value - _lastAddress);
                 Count = range / length;
             }
 
index 3edaf7125e9505b59dcf45d581b8e8bc3a746340..030ef11bb71e9831cc9c1186cc1907ffbe8b2179 100644 (file)
@@ -41,20 +41,24 @@ namespace Microsoft.Diagnostics.Tools.Dump
             }
             if (_commandService.IsCommand(commandName))
             {
-                _commandService.Execute(commandLine, _services);
+                try
+                {
+                    _commandService.Execute(commandLine, _services);
+                    return;
+                }
+                catch (CommandNotSupportedException)
+                {
+                }
             }
-            else
+            if (_sosHost is null)
             {
+                _sosHost = _services.GetService<SOSHost>();
                 if (_sosHost is null)
                 {
-                    _sosHost = _services.GetService<SOSHost>();
-                    if (_sosHost is null)
-                    {
-                        throw new DiagnosticsException($"'{commandName}' command not found");
-                    }
+                    throw new DiagnosticsException($"'{commandName}' command not found");
                 }
-                _sosHost.ExecuteCommand(commandLine);
             }
+            _sosHost.ExecuteCommand(commandLine);
         }
     }
 }
index 9db24ce0fc8db774b1ab32088708aaf393fa14e2..e8fe6766f16943d32719969e4eedbe8ab0f6425d 100644 (file)
@@ -4,7 +4,7 @@
     <TargetFramework>net6.0</TargetFramework>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DbgShimConfigFileName>$(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt</DbgShimConfigFileName>
-    <TestAssetsVersion>1.0.257801</TestAssetsVersion>
+    <TestAssetsVersion>1.0.351101</TestAssetsVersion>
   </PropertyGroup>
 
   <ItemGroup>
index 925403a312f723b02ce16a5b7655ef060783ddf3..b1d21c86cdee80d8b897349b069af0b331916a4a 100644 (file)
@@ -274,7 +274,7 @@ namespace Microsoft.Diagnostics
                 IRuntimeService runtimeService = target.Services.GetService<IRuntimeService>();
                 IRuntime runtime = runtimeService.EnumerateRuntimes().Single();
 
-                CorDebugDataTargetWrapper dataTarget = new(target.Services);
+                CorDebugDataTargetWrapper dataTarget = new(target.Services, runtime);
                 LibraryProviderWrapper libraryProvider = new(target.OperatingSystem, runtime.RuntimeModule.BuildId, runtime.GetDbiFilePath(), runtime.GetDacFilePath());
                 ClrDebuggingVersion maxDebuggerSupportedVersion = new()
                 {
index 4d92f048b50e7c97e63ba51e13976dd147ce4d7f..e85221afc133bf9f2f8814f01514c9ce7a6bfc08 100644 (file)
@@ -7,6 +7,8 @@ using Microsoft.Diagnostics.DebugServices.Implementation;
 using Microsoft.Diagnostics.Runtime;
 using Microsoft.Diagnostics.Runtime.Utilities;
 using Microsoft.FileFormats;
+using Microsoft.FileFormats.ELF;
+using Microsoft.FileFormats.MachO;
 using Microsoft.FileFormats.PE;
 using Microsoft.SymbolStore;
 using Microsoft.SymbolStore.KeyGenerators;
@@ -396,19 +398,19 @@ namespace SOS.Hosting
             {
                 if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                 {
-                    using ELFModule elfModule = ELFModule.OpenFile(filePath);
-                    if (elfModule is not null)
+                    using ELFFile elfFile = Utilities.OpenELFFile(filePath);
+                    if (elfFile is not null)
                     {
-                        return elfModule.BuildID.ToImmutableArray();
+                        return elfFile.BuildID.ToImmutableArray();
                     }
                     throw new ArgumentException($"TestBuildId {filePath} not valid ELF file");
                 }
                 else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                 {
-                    using MachOModule machOModule = MachOModule.OpenFile(filePath);
-                    if (machOModule is not null)
+                    using MachOFile machOFile = Utilities.OpenMachOFile(filePath);
+                    if (machOFile is not null)
                     {
-                        return machOModule.Uuid.ToImmutableArray();
+                        return machOFile.Uuid.ToImmutableArray();
                     }
                     throw new ArgumentException($"TestBuildId {filePath} not valid MachO file");
                 }
@@ -456,14 +458,14 @@ namespace SOS.Hosting
 
         IServiceEvent IHost.OnShutdownEvent => throw new NotImplementedException();
 
+        IServiceEvent<ITarget> IHost.OnTargetCreate => throw new NotImplementedException();
+
         HostType IHost.HostType => HostType.DotnetDump;
 
         IServiceProvider IHost.Services => throw new NotImplementedException();
 
         IEnumerable<ITarget> IHost.EnumerateTargets() => throw new NotImplementedException();
 
-        void IHost.DestroyTarget(ITarget target) => throw new NotImplementedException();
-
         #endregion
 
         #region ICLRDebuggingLibraryProvider* delegates
index d825e02967b6f9139390be7c379ef413397279e3..98e50dbba5325097d608c6c6e0ba8347011cc2fa 100644 (file)
@@ -3,7 +3,7 @@
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
     <DebugServicesConfigFileName>$(OutputPath)$(TargetFramework)\Debugger.Tests.Common.txt</DebugServicesConfigFileName>
-    <TestAssetsVersion>1.0.257801</TestAssetsVersion>
+    <TestAssetsVersion>1.0.351101</TestAssetsVersion>
     <!-- Controls the test asset package restore and the tests that use them -->
     <RunTests>true</RunTests>
   </PropertyGroup>
index 8749b1ceab64fe438e3fd75a33789f996d820303..8ec4976a5e6f6b5948be5fb766d22748a1724f6c 100644 (file)
@@ -16,6 +16,7 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests
     [Command(Name = "runtests", Help = "Runs the debug services xunit tests.")]
     public class RunTestsCommand : CommandBase, ITestOutputHelper
     {
+        [ServiceImport]
         public ITarget Target { get; set; }
 
         [Argument(Help = "Test data xml file path.")]
index 21e0f004084ad11a401c9910743f5a6f757c4b34..bbbefd47e8e8e22fd3559ba846236fa1e361f938 100644 (file)
@@ -97,13 +97,13 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests
 
         public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
 
-        HostType IHost.HostType => HostType.DotnetDump;
+        public IServiceEvent<ITarget> OnTargetCreate { get; } = new ServiceEvent<ITarget>();
 
-        IServiceProvider IHost.Services => throw new NotImplementedException();
+        public HostType HostType => HostType.DotnetDump;
 
-        IEnumerable<ITarget> IHost.EnumerateTargets() => throw new NotImplementedException();
+        public IServiceProvider Services => throw new NotImplementedException();
 
-        void IHost.DestroyTarget(ITarget target) => throw new NotImplementedException();
+        public IEnumerable<ITarget> EnumerateTargets() => throw new NotImplementedException();
 
         #endregion
     }
index 3d3722e1884ecc36ae2e0c68335e072fd1837526..e4851789e281efb3f54e0b7e5ce22b1f60c5e977 100644 (file)
@@ -3,13 +3,15 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.Diagnostics.TestHelpers;
+using System;
 
 namespace Microsoft.Diagnostics.DebugServices.UnitTests
 {
     [Command(Name = "writetestdata", Help = "Writes the test data xml file.")]
     public class WriteTestDataCommand : CommandBase
     {
-        public ITarget Target { get; set; }
+        [ServiceImport]
+        public IServiceProvider Services { get; set; }
 
         [Argument(Name = "FileName", Help = "Test data file path.")]
         public string FileName { get; set; }
@@ -21,7 +23,7 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests
                 throw new DiagnosticsException("Test data file parameter needed");
             }
             var testDataWriter = new TestDataWriter();
-            testDataWriter.Build(Target);
+            testDataWriter.Build(Services);
             testDataWriter.Write(FileName);
             WriteLine($"Test data written to {FileName} successfully");
         }