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.
- 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
- 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).
- [#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.
- 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
## 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
#### 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
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).
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
## 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.
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
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#
+```
+
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
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
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"
_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"
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"
/// </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>
{
if (factory == null)
{
- factory = (services) => Utilities.InvokeConstructor(type, services, optional: true);
+ factory = (services) => Utilities.CreateInstance(type, services);
}
CreateCommand(baseType, commandAttribute, factory);
}
option.AddAlias(alias);
}
}
- else
- {
- // If not an option, add as just a settable properties
- properties.Add((property, null));
- }
}
}
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;
}
}
}
- else
- {
- Type propertyType = property.Property.PropertyType;
- object service = services.GetService(propertyType);
- if (service != null) {
- value = service;
- }
- }
property.Property.SetValue(instance, value);
}
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;
/// </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
/// 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.
/// <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}");
}
/// <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}");
}
/// <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();
}
}
/// <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)
if (!IsThreadEqual(thread, _currentThread))
{
_currentThread = thread;
- ServiceProvider.FlushServices();
+ _serviceContainer.DisposeServices();
OnContextChange.Fire();
}
}
/// <summary>
/// Find the current runtime.
/// </summary>
- public IRuntime GetCurrentRuntime()
+ protected virtual IRuntime GetCurrentRuntime()
{
if (_currentRuntime is null)
{
}
/// <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();
}
}
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);
+ }
+ }
}
}
/// <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)
{
#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;
// 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
+}
/// <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>
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);
try
{
- byte[] data = null;
-
// Read the memory from the PE image found/downloaded above
PEMemoryBlock block = reader.GetEntireImage();
if (rva < block.Length)
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)
{
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)
{
#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)
{
}
}
}
- 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
// 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))
{
// 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();
}
}
}
--- /dev/null
+// 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;
+ }
+ }
+}
/// 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>
// 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))
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
{
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;
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
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
{
{
return true;
}
- else
- {
- GetPEInfo();
- return (_flags & Flags.IsPEImage) != 0;
- }
+ Services.GetService<PEFile>();
+ return (_flags & Flags.IsPEImage) != 0;
}
}
{
get
{
- GetPEInfo();
+ Services.GetService<PEFile>();
return (_flags & Flags.IsManaged) != 0;
}
}
{
get
{
- GetPEInfo();
+ Services.GetService<PEFile>();
if ((_flags & Flags.IsFileLayout) != 0)
{
return true;
public IEnumerable<PdbFileInfo> GetPdbFileInfos()
{
- GetPEInfo();
+ Services.GetService<PEFile>();
Debug.Assert(_pdbFileInfos is not null);
return _pdbFileInfos;
}
{
try
{
- Stream stream = ModuleService.RawMemoryService.CreateMemoryStream();
+ Stream stream = ModuleService.MemoryService.CreateMemoryStream();
var elfFile = new ELFFile(new StreamAddressSpace(stream), ImageBase, true);
if (elfFile.IsValid())
{
{
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);
{
Version version = null;
- PEFile peFile = GetPEInfo();
- if (peFile != null)
+ PEFile peFile = Services.GetService<PEFile>();
+ if (peFile is not null)
{
try
{
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)
--- /dev/null
+// 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; }
+ }
+}
/// <summary>
/// Module service base implementation
/// </summary>
- public abstract class ModuleService : IModuleService
+ public abstract class ModuleService : IModuleService, IDisposable
{
[Flags]
internal enum ELFProgramHeaderAttributes : uint
// 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;
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
/// <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;
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) {
/// </summary>
private Dictionary<ulong, IModule> GetModules()
{
- if (_modules == null)
+ if (_modules is null)
{
_modules = GetModulesInner();
}
/// <returns></returns>
private IModule[] GetSortedModules()
{
- if (_sortedByBaseAddress == null)
+ if (_sortedByBaseAddress is null)
{
_sortedByBaseAddress = GetModules().OrderBy((pair) => pair.Key).Select((pair) => pair.Value).ToArray();
}
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())
{
// 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
{
/// </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
{
{
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();
}
}
- 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
private string _versionString;
public ModuleFromDataReader(ModuleServiceFromDataReader moduleService, int moduleIndex, ModuleInfo moduleInfo, ulong imageSize)
- : base(moduleService.Target)
+ : base(moduleService.Services)
{
_moduleService = moduleService;
_moduleInfo = moduleInfo;
#region IModule
- public override int ModuleIndex { get; }
-
public override string FileName => _moduleInfo.FileName;
public override ulong ImageBase => _moduleInfo.ImageBase;
}
else
{
- if (_moduleService.Target.OperatingSystem != OSPlatform.Windows)
- {
- _version = GetVersionInner();
- }
+ _version = GetVersionInner();
}
}
return _version;
{
if (InitializeValue(Module.Flags.InitializeProductVersion))
{
- if (_moduleService.Target.OperatingSystem != OSPlatform.Windows && !IsPEImage)
- {
- _versionString = _moduleService.GetVersionString(this);
- }
+ _versionString = GetVersionStringInner();
}
return _versionString;
}
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;
}
try
{
modules.Add(moduleInfo.ImageBase, module);
+ moduleIndex++;
}
catch (ArgumentException)
{
Trace.TraceError($"GetModules(): duplicate module base '{module}' dup '{modules[moduleInfo.ImageBase]}'");
}
}
-
- moduleIndex++;
}
return modules;
}
--- /dev/null
+// 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
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) {
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; }
#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)
OSPlatform platform = Target.OperatingSystem;
string filePath = null;
- if (SymbolService.IsSymbolStoreEnabled)
+ if (_symbolService.IsSymbolStoreEnabled)
{
SymbolStoreKey key = null;
if (key is not null)
{
// Now download the DAC module from the symbol server
- filePath = SymbolService.DownloadFile(key);
+ filePath = _symbolService.DownloadFile(key);
}
}
else
return filePath;
}
- private ISymbolService SymbolService => _symbolService ??= Target.Services.GetService<ISymbolService>();
-
public override bool Equals(object obj)
{
IRuntime runtime = (IRuntime)obj;
}
private static readonly string[] s_runtimeTypeNames = {
+ "Unknown",
"Desktop .NET Framework",
".NET Core",
".NET Core (single-file)",
- "Unknown"
+ "Other"
};
public override string ToString()
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();
}
--- /dev/null
+// 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
+ }
+}
// 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;
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
{
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());
}
}
}
- private readonly LinkedListNode _events = new LinkedListNode();
+ private readonly LinkedListNode _events = new();
public ServiceEvent()
{
}
}
}
+
+ /// <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);
+ }
+ }
+ }
}
--- /dev/null
+// 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;
+ }
+}
+++ /dev/null
-// 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;
- }
- }
-}
// 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;
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))
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)
{
// 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;
/// <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)
{
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
/// <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).
}
/// <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)
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());
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();
}
}
}
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
public ITarget Target => _threadService.Target;
- public IServiceProvider Services => ServiceProvider;
+ public IServiceProvider Services => _serviceContainer;
public bool TryGetRegisterValue(int index, out ulong value)
{
/// <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;
break;
default:
- throw new PlatformNotSupportedException($"Unsupported architecture: {target.Architecture}");
+ throw new PlatformNotSupportedException($"Unsupported architecture: {Target.Architecture}");
}
var registers = new List<RegisterInfo>();
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;
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>
/// </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;
using Microsoft.Diagnostics.Runtime.DataReaders.Implementation;
using System;
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
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;
// 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;
using System.Linq;
using System.Reflection;
using System.Reflection.PortableExecutable;
+using System.Runtime.InteropServices;
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>
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);
/// <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);
}
}
- 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)
{
/// <summary>
/// Console service
/// </summary>
+ [ServiceImport]
public IConsoleService Console { get; set; }
/// <summary>
Console.Write(message);
}
+ /// <summary>
+ /// Display a blank line
+ /// </summary>
+ protected void WriteLine()
+ {
+ Console.WriteLine();
+ Console.CancellationToken.ThrowIfCancellationRequested();
+ }
+
/// <summary>
/// Display line
/// </summary>
/// <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);
- }
}
}
{
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>
/// <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>();
}
}
{
}
}
+
+ /// <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)
+ {
+ }
+ }
}
/// 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
// See the LICENSE file in the project root for more information.
using System;
-using System.Threading;
namespace Microsoft.Diagnostics.DebugServices
{
--- /dev/null
+// 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
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>
/// 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);
}
}
// 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
{
/// <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);
}
}
/// </summary>
public enum RuntimeType
{
- Desktop = 0,
- NetCore = 1,
- SingleFile = 2,
- Unknown = 3
+ Unknown = 0,
+ Desktop = 1,
+ NetCore = 2,
+ SingleFile = 3,
+ Other = 4
}
/// <summary>
--- /dev/null
+// 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);
+ }
+}
/// </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);
+ }
}
--- /dev/null
+// 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);
+ }
+}
/// Invoked when the target is destroyed.
/// </summary>
IServiceEvent OnDestroyEvent { get; }
+
+ /// <summary>
+ /// Cleans up the target and releases target's resources.
+ /// </summary>
+ void Destroy();
}
}
--- /dev/null
+// 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);
+ }
+}
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+// 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()
+ {
+ }
+ }
+}
--- /dev/null
+// 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()
+ {
+ }
+ }
+}
--- /dev/null
+// 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();
+ }
+ }
+}
// See the LICENSE file in the project root for more information.
using System;
-using System.Collections.Generic;
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));
}
}
}
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;
- }
}
}
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;
[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).")]
$"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>
[Argument(Help = "The address of a ConcurrentDictionary object.")]
public string Address { get; set; }
+ [ServiceImport]
public ClrRuntime Runtime { get; set; }
public override void ExtensionInvoke()
[Argument(Help = "The address of a ConcurrentQueue object.")]
public string Address { get; set; }
+ [ServiceImport]
public ClrRuntime Runtime { get; set; }
public override void ExtensionInvoke()
/// <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()
[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.")]
--- /dev/null
+// 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");
+ }
+ }
+ }
+}
[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.")]
[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()
}
}
+ [ServiceImport]
public ITarget Target { get; set; }
+ [ServiceImport]
public IMemoryService MemoryService { get; set; }
void DisplaySegments(IModule module)
[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.")]
[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; }
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)
{
}
}
- public static class Utilities
+ public static class CommandUtilities
{
public static string ToHex(this ImmutableArray<byte> array) => string.Concat(array.Select((b) => b.ToString("x2")));
}
[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.")]
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.")]
{
SymbolService.AddDirectoryPath(Directory);
}
- if (LoadSymbols)
+ if (LoadSymbols && ModuleService is not null)
{
foreach (IModule module in ModuleService.EnumerateModules())
{
[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.")]
[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()
[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.")]
+++ /dev/null
-// 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");
- }
- }
- }
-}
using Microsoft.Diagnostics.DebugServices;
using System;
using System.Collections.Immutable;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
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())
{
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())
{
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");
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))
{
{
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;
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);
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
}
-using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.DebugServices;
+using System;
+using System.IO;
namespace Microsoft.Diagnostics.TestHelpers
{
_debuggerServices = debuggerServices;
}
- public override IThread GetCurrentThread()
+ protected override IThread GetCurrentThread()
{
HResult hr = _debuggerServices.GetCurrentThreadId(out uint threadId);
if (hr != HResult.S_OK)
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)
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);
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;
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.
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);
{
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
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)
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)
{
}
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)
{
Trace.TraceInformation("HostServices.DestroyTarget #{0}", _target != null ? _target.Id : "<none>");
try
{
- if (_target != null)
- {
- DestroyTarget(_target);
- }
+ _target?.Destroy();
+ _target = null;
}
catch (Exception ex)
{
return HResult.S_OK;
}
}
+ catch (CommandNotSupportedException)
+ {
+ return HResult.E_NOTIMPL;
+ }
catch (Exception ex)
{
Trace.TraceError(ex.ToString());
return HResult.E_INVALIDARG;
}
}
+ catch (CommandNotSupportedException)
+ {
+ return HResult.E_NOTIMPL;
+ }
catch (Exception ex)
{
Trace.TraceError(ex.ToString());
{
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)
{
/// </summary>
internal class MemoryServiceFromDebuggerServices : IMemoryService
{
- private readonly ITarget _target;
private readonly DebuggerServices _debuggerServices;
/// <summary>
{
Debug.Assert(target != null);
Debug.Assert(debuggerServices != null);
- _target = target;
_debuggerServices = debuggerServices;
switch (target.Architecture)
/// </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
ulong imageBase,
ulong imageSize,
uint indexFileSize,
- uint indexTimeStamp)
- : base(moduleService.Target)
+ uint indexTimeStamp)
+ : base(moduleService.Services)
{
_moduleService = moduleService;
ModuleIndex = moduleIndex;
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()
{
}
else
{
- if (_moduleService.Target.OperatingSystem != OSPlatform.Windows)
- {
- _version = GetVersionInner();
- }
+ _version = GetVersionInner();
}
}
return _version;
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;
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)
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;
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)
}
// 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();
}
}
}
using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.DebugServices.Implementation;
using Microsoft.Diagnostics.Runtime.Utilities;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
{
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;
[Argument(Name = "arguments", Help = "Arguments to SOS command.")]
public string[] Arguments { get; set; }
+ [ServiceImport]
public SOSHost SOSHost { get; set; }
public override void Invoke()
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>();
// 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;
{
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>();
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,
IntPtr self,
out uint threadId)
{
- uint? id = _services.GetService<IThread>()?.ThreadId;
+ uint? id = _contextService.GetCurrentThread()?.ThreadId;
if (id.HasValue)
{
threadId = id.Value;
IntPtr self,
out ulong address)
{
- address = _runtimeBaseAddress;
+ address = _runtime.RuntimeModule.ImageBase;
return HResult.S_OK;
}
using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime.Utilities;
using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
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));
AddRef();
}
- protected override void Destroy() => ServiceWrapper.Dispose();
+ protected override void Destroy()
+ {
+ Trace.TraceInformation("HostWrapper.Destroy");
+ ServiceWrapper.Dispose();
+ }
#region IHost
/// <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;
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);
}
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.
AddRef();
}
+ void IDisposable.Dispose()
+ {
+ Trace.TraceInformation("RuntimeWrapper.Dispose");
+ this.ReleaseWithCheck();
+ }
+
protected override void Destroy()
{
Trace.TraceInformation("RuntimeWrapper.Destroy");
// 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;
}
}
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) {
}
finally
{
- dataTarget.Release();
+ dataTarget.ReleaseWithCheck();
}
}
Build = 0,
Revision = 0,
};
- var dataTarget = new CorDebugDataTargetWrapper(_services);
+ var dataTarget = new CorDebugDataTargetWrapper(_services, _runtime);
ulong clrInstanceId = _runtime.RuntimeModule.ImageBase;
int hresult = 0;
try
}
finally
{
- dataTarget.Release();
+ dataTarget.ReleaseWithCheck();
}
}
/// <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))
{
if (!_disposed)
{
_disposed = true;
- TargetWrapper.Release();
- COMHelper.Release(_interface);
+ ComWrapper.ReleaseWithCheck(_interface);
}
}
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);
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);
}
{
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)
{
IntPtr self,
out uint sysId)
{
- IThread thread = Services.GetService<IThread>();
+ IThread thread = ContextService.GetCurrentThread();
if (thread is not null)
{
sysId = thread.ThreadId;
IntPtr self,
ulong* offset)
{
- IThread thread = Services.GetService<IThread>();
+ IThread thread = ContextService.GetCurrentThread();
if (thread is not null)
{
try
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))
}
}
}
+
+ 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);
+ }
+ }
}
/// <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(
private const string SOSInitialize = "SOSInitializeByHost";
private const string SOSUninitialize = "SOSUninitializeByHost";
- private readonly IContextService _contextService;
private readonly HostWrapper _hostWrapper;
private IntPtr _sosLibrary = IntPtr.Zero;
/// </summary>
public string SOSPath { get; set; }
+ [ServiceExport(Scope = ServiceScope.Global)]
public static SOSLibrary Create(IHost host)
{
SOSLibrary sosLibrary = null;
catch
{
sosLibrary.Uninitialize();
- sosLibrary = null;
throw;
}
- host.OnShutdownEvent.Register(() => {
- sosLibrary.Uninitialize();
- sosLibrary = null;
- });
return sosLibrary;
}
/// <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>
Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.FreeLibrary(_sosLibrary);
_sosLibrary = IntPtr.Zero;
}
- _hostWrapper.Release();
+ _hostWrapper.ReleaseWithCheck();
}
/// <summary>
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>();
}
}
Trace.TraceInformation("ServiceWrapper.Dispose");
foreach (var wrapper in _wrappers.Values)
{
- wrapper.Release();
+ wrapper.ReleaseWithCheck();
}
_wrappers.Clear();
}
if (wrapper == null) {
return HResult.E_NOINTERFACE;
}
- wrapper.AddRef();
return COMHelper.QueryInterface(wrapper.IUnknownObject, guid, out ptr);
}
}
_symbolService = symbolService;
_memoryService = memoryService;
_ignoreAddressBitsMask = memoryService.SignExtensionMask();
- Debug.Assert(_symbolService != null);
VTableBuilder builder = AddInterface(IID_ISymbolService, validate: false);
builder.AddMethod(new ParseSymbolPathDelegate(ParseSymbolPath));
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
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);
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(
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;
#
SOSCOMMAND:SOSStatus
-VERIFY:.*\.NET Core .*runtime at.*\s+
+VERIFY:.*\.NET Core .*runtime.*at.*\s+
IFDEF:TRIAGE_DUMP
SOSCOMMAND:setclrpath %DESKTOP_RUNTIME_PATH%
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
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()
{
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();
{
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;
{
if (m_pSymbolService == nullptr)
{
- ITarget* target = GetTarget();
+ ITarget* target = GetTarget();
if (target != nullptr)
{
target->GetService(__uuidof(ISymbolService), (void**)&m_pSymbolService);
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;
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())
{
goto exit;
}
+ address = module.ResolveFileAddress(offset);
+ if (!address.IsValid())
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+
if (module != address.GetModule())
{
hr = E_INVALIDARG;
file = m_debugger.GetOutputFileHandle();
}
fputs(str, file);
+ fflush(file);
}
HRESULT
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()
{
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();
{
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)
_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;
_consoleService.Start((string prompt, string commandLine, CancellationToken cancellation) =>
{
_fileLoggingConsoleService.WriteLine("{0}{1}", prompt, commandLine);
- _commandService.Execute(commandLine, _contextService.Services);
+ _commandService.Execute(commandLine, contextService.Services);
});
}
}
}
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);
}
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}");
- }
- }
}
}
[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
[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;
}
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;
}
}
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);
}
}
}
<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>
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()
{
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;
{
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");
}
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
<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>
[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.")]
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
}
// 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; }
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");
}