EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests", "src\tests\Microsoft.Diagnostics.Monitoring.EventPipe\Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests.csproj", "{A58B7F72-E9DF-4962-9E15-300EF4C53ABB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-stack", "src\Tools\dotnet-stack\dotnet-stack.csproj", "{642E6E67-8A84-473D-9507-C302BF10989D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-stack", "src\Tools\dotnet-stack\dotnet-stack.csproj", "{642E6E67-8A84-473D-9507-C302BF10989D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackTracee", "src\tests\StackTracee\StackTracee.csproj", "{57EB54E7-521A-4CDE-9DAE-8D6AE90A20BB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackTracee", "src\tests\StackTracee\StackTracee.csproj", "{57EB54E7-521A-4CDE-9DAE-8D6AE90A20BB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.DebugServices.Implementation", "src\Microsoft.Diagnostics.DebugServices.Implementation\Microsoft.Diagnostics.DebugServices.Implementation.csproj", "{1F23F5DA-F766-445F-88FF-86137BDC2709}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
{57EB54E7-521A-4CDE-9DAE-8D6AE90A20BB}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{57EB54E7-521A-4CDE-9DAE-8D6AE90A20BB}.RelWithDebInfo|x86.ActiveCfg = Debug|Any CPU
{57EB54E7-521A-4CDE-9DAE-8D6AE90A20BB}.RelWithDebInfo|x86.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|ARM.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|x64.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Checked|x86.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|ARM.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|x64.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Debug|x86.Build.0 = Debug|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|ARM.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|ARM.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|ARM64.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|x64.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|x64.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|x86.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.Release|x86.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {1F23F5DA-F766-445F-88FF-86137BDC2709}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{A58B7F72-E9DF-4962-9E15-300EF4C53ABB} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{642E6E67-8A84-473D-9507-C302BF10989D} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{57EB54E7-521A-4CDE-9DAE-8D6AE90A20BB} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
+ {1F23F5DA-F766-445F-88FF-86137BDC2709} = {19FAB78C-3351-4911-8F0C-8C6056401740}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
--- /dev/null
+# Diagnostic Tools Extensibility
+
+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. The implementation of new commands in C# is faster/more productive for each future command our team needs to add. Other people on .NET team and in the community are more likely to contribute improvements into 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 "untified extensiblity" where various teams are coming together to define a common debugger abstraction across all of the debuggers and debugger like hosts (dotnet-dump). Azure Watson could use this infrastructure to write a !analyze like commands and other analysis tools using a very subset of the DAC (as a service) to do some unhandled exception and stack trace triage.
+
+## Goals
+
+- Provide a simple set of services that hosts can implement and commands/services use.
+- Easy use of the ClrMD API in commands and services.
+- Host the same commands/command assemblies under various "hosts" like:
+ - The dotnet-dump REPL
+ - The lldb debugger
+ - The Windows debuggers (windbg/cdb)
+ - Visual Studio
+ - Create various "target" types from the command line or a command from:
+ - Windows minidumps
+ - Linux coredumps
+ - Live process snapshots
+
+## Customer Value
+
+- Improve our CSS engineer experience by providing commands in Visual Studio that keep them from needed to switch to windbg.
+ - 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.
+ - 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).
+
+- Improve our internal .NET team productivity and inner loop development by providing these commands and the native SOS command under Visual Studio. See issue [#1397](https://github.com/dotnet/diagnostics/issues/1397).
+
+- This plan would allow these ClrMD based commands to run across all our debuggers (dotnet-dump, windbg, lldb and Visual Studio):
+ - Criteo's 5 or so extension commands:
+ - timerinfo - display running timers details.
+ - pstack - display the parallel stacks
+ - rest of list TBD
+
+- Existing ClrMD commands like "clrmodules" which displays the version info of the managed assemblies in the dump or process.
+
+- List of issues that will be addressed by this work or has inspired it:
+ - [#1397](https://github.com/dotnet/diagnostics/issues/1397) and [#40182](https://github.com/dotnet/runtime/issues/40182) "SOS Plugin for Visual Studio". Create a VS package that allows the above extension commands to be run and various native SOS commands.
+ - [#1016](https://github.com/dotnet/diagnostics/issues/1016) This work address this issue with the internal native SOS cleanup of the target OS platform.
+ - [#1001](https://github.com/dotnet/diagnostics/issues/1001) "SOS Hosting layer needs concept of Target". Fixes commands like `eeversion` and module name comparing when the native SOS is hosted under dotnet-dump and VS.
+ - [#565](https://github.com/dotnet/diagnostics/issues/565) "Using SOS programmatically". This plan will help enable the work described in this issue.
+ - [#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.
+ - [#562](https://github.com/dotnet/diagnostics/issues/562) "Some CLRMD APIs used in commands under dotnet-dump may fail". This fixes the ClrMD based commands that need type and method info from the modules with missing metadata.
+ - [#479](https://github.com/dotnet/diagnostics/issues/479) "SOS: flush the global state that contains per-process info". This plan moves all of this global state into ITarget/IRuntime which are provided by the host (i.e. dotnet-dump, VS, etc.)
+ - [#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.
+ - [#186](https://github.com/dotnet/diagnostics/issues/186) "dotnet dump extensibility". This is the parent issue that inspired this plan.
+ - [#174](https://github.com/dotnet/diagnostics/issues/174) "List all managed dlls in lldb". This work will allow the `clrmodules` command to work on lldb, windbg and dotnet-dump.
+
+## Road Map
+
+1. Add the native target/runtime hosting/wrapper support. These changes are to the native SOS code to use target/runtime interfaces (a new ITarget/existing IRuntime) that the host layer provides. This cleans up most of the global state in the native SOS code allowing dotnet-dump to handle multi-target commands, switching between targets (dumps) and switching between runtimes in the process.
+2. Create lldb host/target allowing extensions commands to run under lldb.
+3. Create dbgeng/windbg host/target allowing extension commands to run under windbg.
+4. Create a VS host/target package allowing SOS and the extension commands to be run from VS using the Concord API.
+5. 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.
+
+- ClrMD commands and possible analysis engine
+ - Basic target info like architecture, etc.
+ - Console service
+ - Memory services
+ - Simple set of module services
+ - Simple set of thread services
+ - Mechanism to expose ClrInfo/ClrRuntime instances
+- Native AOT commands
+ - Memory services
+ - Console service
+ - Command service
+ - Simple module services
+ - Simple set of symbol services
+ - Mechanism to expose Runtime Snapshot Parse API.
+- dotnet-dump and VS that host the native SOS code
+ - Target and runtime services
+ - Console service
+ - Command service
+ - Memory services
+ - Richer module services
+ - Thread services
+ - Symbol and download services
+
+The threading model is single-threaded mainly because native debuggers like dbgeng are basically single-threaded and using async makes the implementation and the over all infrastructre way more complex.
+
+#### Interface Hierarchy
+
+- IHost
+ - Global services
+ - IConsoleService
+ - ICommandService
+ - ISymbolService
+ - IDumpTargetFactory
+ - IProcessSnapshotTargetFactory
+ - ITarget
+ - Per-target services
+ - IRuntimeService
+ - IRuntime
+ - ClrInfo
+ - ClrRuntime
+ - Runtime Snapshot Parse instance
+ - IMemoryService
+ - IModuleService
+ - IModule
+ - IThreadService
+ - IThread
+ - SOSHost
+ - ClrMDHelper
+
+## Hosts
+
+The host is the debugger or program the command and the infrastructure runs. The goal is to allow the same code for a command to run under different programs like the dotnet-dump REPL, the lldb and Windows debuggers. Under Visual Studio the host will be a VS extension package.
+
+#### IHost
+
+The host implements this interface to provide to the rest the global services, current target and the type of host.
+
+See [IHost.cs](../../src/Microsoft.Diagnostics.DebugServices/IHost.cs) and [host.h](../../src/SOS/inc/host.h) for details.
+
+## Targets
+
+A data target represents the dump, process snapshot or live target session. It provides information and services for the target like architecture, native process, thread, and module services. For commands like gcheapdiff, the ability to open and access a second dump or process snapshot is necessary.
+
+Because of the various hosting requirements, ClrMD's IDataReader and DataTarget should not be exposed directly and instead use one of the following interfaces and services. This allows this infrastructure provide extra functionality to ClrMD commands like the address space module mapping.
+
+The "gcdump" target is a possible target that allows gcdump specific services and commands to be executed in a REPL. None of the SOS commands or ClrMD services will work but it will be easy to provide services and commands specific to the gcdump format. This may require some kind of command filtering by the target or the target provides the command service.
+
+#### 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.
+
+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.
+
+For Windbg/cdb, these services will be implemented on the dbgeng API.
+
+For lldb, these services will be implemented on the lldb extension API via some new pinvoke wrappers.
+
+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.
+
+### IDumpTargetFactory
+
+This global service allows dump ITargets to be created.
+
+See [IDumpTargetFactory.cs](../../src/Microsoft.Diagnostics.DebugServices/IDumpTargetFactory.cs) for more details.
+
+### ICommandService
+
+This service provides the parsing, dispatching and executing of standardized commands. It is implemented using System.Commandline but there should be no dependencies on System.CommandLine exposed to the commands or other services. It is an implementation detail.
+
+See [ICommandService.cs](../../src/Microsoft.Diagnostics.DebugServices/ICommandService.cs)
+
+### IConsoleService
+
+Abstracts the console output across all the platforms.
+
+See [IConsoleService.cs](../../src/Microsoft.Diagnostics.DebugServices/IConsoleService.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.
+
+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.
+
+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).
+
+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).
+
+### IThreadService and IThread
+
+Abstracts the hosting debuggers native threads. There are functions to enumerate, get details and context about native threads.
+
+See [IThreadService.cs](../../src/Microsoft.Diagnostics.DebugServices/IThreadService.cs) and [IThread.cs](../../src/Microsoft.Diagnostics.DebugServices/IThread.cs)
+
+### IModuleService and IModule
+
+Abstracts the modules in the target. Provides the details the name, base address, build id, version, etc. Some targets this includes both native and managed modules (Windows dbgeng, Linux dotnet-dump ELF dumps) but there are hosts/targets (Linux/MacOS lldb) that only provide the native modules.
+
+One issues that may need to be addressed is that some platforms (MacOS) have non-contiguous memory sections in the module so the basic ImageSize isn't enough. May need (maybe only internally) need some concept of "sections" (address, size) and/or "header size". One of the main uses of the ImageBase/ImageSize is to create a memory stream of the PE or module header to extract module details like version, build id, or search for embedded version string.
+
+See [IModuleService.cs](../../src/Microsoft.Diagnostics.DebugServices/IModuleService.cs) and [IModule.cs](../../src/Microsoft.Diagnostics.DebugServices/IModule.cs).
+
+### ISymbolService
+
+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.
+
+### IRuntimeService/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.
+
+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.
+
+### SOSHost
+
+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).
+
+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.
+
+### IHostServices
+
+This interface provides services to the native SOS/plugins code. It is a private interface between the native SOS code and the SOS.Extensions host. There are services to register the IDebuggerService instance, dispatch commands and create/detroy target instance.
+
+[hostservices.h](../../src/SOS/inc/hostservices.h) for details.
+
+### IDebuggerService
+
+This native interface is what the SOS.Extensions host uses to implement the above services. This is another private interface between SOS.Extensions and the native lldb plugin or Windows SOS native code.
+
+[debuggerservice.h](../../src/SOS/inc/debuggerservice.h) for details.
+
+## Projects and Assemblies
+
+### SOS.Extensions (new)
+
+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)
+
+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.
+
+### SOS.Hosting
+
+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)
+
+Contains the common debug services implementations used by dotnet-dump and SOS.Extensions (dbgeng/lldb) hosts.
+
+### Microsoft.Diagnostics.ExtensionCommands (new)
+
+Contains the common commands shared with dotnet-dump and the SOS.Extensions hosts.
+
+### Microsoft.Diagnostics.Repl
+
+The command and console service implemenations.
+
+### dotnet-dump
+
+The dump collection and analysis REPL global tools. It hosts the extensions layer and debug services using ClrMD's Linux and Windows minidump data readers.
+
+### lldbplugin
+
+The lldb plugin that provides debugger services (IDebuggerServices) to SOS.Extensions and LLDBServices to native SOS. It displays both the native SOS and new managed extension commands. It initializes the managed hosting layer via the "extensions" native library.
+
+### Strike (SOS)
+
+Native SOS commands and code.
+
+On Windows, it provides the debugger services (IDebuggerServices) to SOS.Extensions and initializes the managed hosting layer via the "extensions" native library.
+
\ No newline at end of file
--- /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 Microsoft.SymbolStore;
+using Microsoft.SymbolStore.KeyGenerators;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+#nullable enable
+
+ /// <summary>
+ /// A ClrMD symbol locator that search binaries based on files loaded in the live Linux target.
+ /// </summary>
+ internal class BinaryLocator : IBinaryLocator
+ {
+ private readonly ISymbolService _symbolService;
+
+ public BinaryLocator(ITarget target)
+ {
+ _symbolService = target.Services.GetService<ISymbolService>();
+ }
+
+ public string? FindBinary(string fileName, int buildTimeStamp, int imageSize, bool checkProperties)
+ {
+ Trace.TraceInformation($"FindBinary: {fileName} buildTimeStamp {buildTimeStamp:X8} imageSize {imageSize:X8}");
+
+ if (_symbolService.IsSymbolStoreEnabled)
+ {
+ SymbolStoreKey? key = PEFileKeyGenerator.GetKey(fileName, (uint)buildTimeStamp, (uint)imageSize);
+ if (key != null)
+ {
+ // Now download the module from the symbol server, cache or from a directory
+ return _symbolService.DownloadFile(key);
+ }
+ else
+ {
+ Trace.TraceInformation($"DownloadFile: {fileName}: key not generated");
+ }
+ }
+ else
+ {
+ Trace.TraceInformation($"DownLoadFile: {fileName}: symbol store not enabled");
+ }
+
+ return null;
+ }
+
+ public string? FindBinary(string fileName, ImmutableArray<byte> buildId, bool checkProperties)
+ {
+ Trace.TraceInformation($"FindBinary: {fileName} buildid {buildId}");
+ return null;
+ }
+
+ public Task<string?> FindBinaryAsync(string fileName, ImmutableArray<byte> buildId, bool checkProperties)
+ {
+ Trace.TraceInformation($"FindBinaryAsync: {fileName} buildid {buildId}");
+ return Task.FromResult<string?>(null);
+ }
+
+ public Task<string?> FindBinaryAsync(string fileName, int buildTimeStamp, int imageSize, bool checkProperties)
+ {
+ Trace.TraceInformation($"FindBinaryAsync: {fileName} buildTimeStamp {buildTimeStamp:X8} imageSize {imageSize:X8}");
+ return Task.FromResult<string?>(null);
+ }
+ }
+}
--- /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.Implementation
+{
+ public sealed class MemoryCache
+ {
+ /// <summary>
+ /// This class represents a chunk of cached memory, more or less a page.
+ /// </summary>
+ class Cluster
+ {
+ internal const int Size = 4096;
+
+ /// <summary>
+ /// Empty cluster
+ /// </summary>
+ internal static Cluster Empty = new Cluster(Array.Empty<byte>());
+
+ /// <summary>
+ /// The cached data.
+ /// </summary>
+ private readonly byte[] _data;
+
+ /// <summary>
+ /// Creates a cluster for some data.
+ /// If the buffer is shorter than a page, it build a validity bitmap for it.
+ /// </summary>
+ /// <param name="data">the data to cache</param>
+ ///
+ internal Cluster(byte[] data)
+ {
+ _data = data;
+ }
+
+ /// <summary>
+ /// Computes the base address of the cluster holding an address.
+ /// </summary>
+ /// <param name="address">input address</param>
+ /// <returns>start address of the cluster</returns>
+ internal static ulong GetBase(ulong address)
+ {
+ return address & ~(ulong)(Cluster.Size - 1);
+ }
+
+ /// <summary>
+ /// Computes the offset of an address inside of the cluster.
+ /// </summary>
+ /// <param name="address">input address</param>
+ /// <returns>offset of address</returns>
+ internal static int GetOffset(ulong address)
+ {
+ return unchecked((int)((uint)address & (Cluster.Size - 1)));
+ }
+
+ /// <summary>
+ /// Reads at up <paramref name="size"/> bytes from location <paramref name="address"/>.
+ /// </summary>
+ /// <param name="address">desired address</param>
+ /// <param name="buffer">buffer to read</param>
+ /// <param name="size">number of bytes to read</param>
+ /// <returns>bytes read</returns>
+ internal int ReadBlock(ulong address, Span<byte> buffer, int size)
+ {
+ int offset = GetOffset(address);
+ if (offset < _data.Length)
+ {
+ size = Math.Min(_data.Length - offset, size);
+ new Span<byte>(_data, offset, size).CopyTo(buffer);
+ }
+ else
+ {
+ size = 0;
+ }
+ return size;
+ }
+ }
+
+ /// <summary>
+ /// After memory cache reaches the limit size, it gets flushed upon next access.
+ /// </summary>
+ private const int CacheSizeLimit = 64 * 1024 * 1024; // 64 MB
+
+ /// <summary>
+ /// The delegate to the actual read memory
+ /// </summary>
+ public delegate byte[] ReadMemoryDelegate(ulong address, int size);
+
+ private readonly Dictionary<ulong, Cluster> _map;
+ private readonly ReadMemoryDelegate _readMemory;
+
+ public MemoryCache(ReadMemoryDelegate readMemory)
+ {
+ _map = new Dictionary<ulong, Cluster>();
+ _readMemory = readMemory;
+ }
+
+ /// <summary>
+ /// Current size of this memory cache
+ /// </summary>
+ public long CacheSize { get; private set; }
+
+ /// <summary>
+ /// Flush this memory cache
+ /// </summary>
+ public void FlushCache()
+ {
+ Trace.TraceInformation("Flushed memory cache");
+ _map.Clear();
+ CacheSize = 0;
+ }
+
+ /// <summary>
+ /// Reads up to <paramref name="buffer.Length"/> bytes of memory at <paramref name="address"/>.
+ /// It walks the set of clusters to collect as much data as possible.
+ /// </summary>
+ /// <param name="address">address to read</param>
+ /// <param name="buffer">span of buffer to read memory</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if read memory succeeded or partially succeeded</returns>
+ public bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead)
+ {
+ int bytesRequested = buffer.Length;
+ int offset = 0;
+
+ while (bytesRequested > 0)
+ {
+ Cluster cluster = GetCluster(address);
+ int read = cluster.ReadBlock(address, buffer.Slice(offset), bytesRequested);
+ if (read <= 0) {
+ break;
+ }
+ address += (uint)read;
+ offset += read;
+ bytesRequested -= read;
+ }
+
+ bytesRead = offset;
+ return offset > 0;
+ }
+
+ /// <summary>
+ /// Ensures that an address is cached.
+ /// </summary>
+ /// <param name="address">target address</param>
+ /// <returns>It will resolve to an existing cluster or a newly-created one</returns>
+ private Cluster GetCluster(ulong address)
+ {
+ ulong baseAddress = Cluster.GetBase(address);
+
+ if (!_map.TryGetValue(baseAddress, out Cluster cluster))
+ {
+ if (CacheSize >= CacheSizeLimit)
+ {
+ FlushCache();
+ }
+
+ // There are 3 things that can happen here:
+ // 1) Normal full size cluster read (== Cluster.Size). The full block memory is cached.
+ // 2) Partial cluster read (< Cluster.Size). The partial memory block is cached and the memory after it is invalid.
+ // 3) Data == null. Read failure. Failure is not cached.
+ byte[] data = _readMemory(baseAddress, Cluster.Size);
+ if (data == null)
+ {
+ cluster = Cluster.Empty;
+ }
+ else
+ {
+ cluster = new Cluster(data);
+ CacheSize += data.Length;
+ _map[baseAddress] = cluster;
+ }
+ }
+
+ return cluster;
+ }
+ }
+}
--- /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;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Memory service implementation for the clrmd IDataReader
+ /// </summary>
+ public class MemoryServiceFromDataReader : IMemoryService
+ {
+ private readonly IDataReader _dataReader;
+
+ /// <summary>
+ /// Memory service constructor
+ /// </summary>
+ /// <param name="dataReader">CLRMD data reader</param>
+ public MemoryServiceFromDataReader(IDataReader dataReader)
+ {
+ _dataReader = dataReader;
+ }
+
+ #region IMemoryService
+
+ /// <summary>
+ /// Returns the pointer size of the target
+ /// </summary>
+ public int PointerSize => _dataReader.PointerSize;
+
+ /// <summary>
+ /// Read memory out of the target process.
+ /// </summary>
+ /// <param name="address">The address of memory to read</param>
+ /// <param name="buffer">The buffer to read memory into</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
+ public bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead)
+ {
+ bytesRead = _dataReader.Read(address, buffer);
+ return bytesRead > 0;
+ }
+
+ /// <summary>
+ /// Write memory into target process for supported targets.
+ /// </summary>
+ /// <param name="address">The address of memory to write</param>
+ /// <param name="buffer">The buffer to write</param>
+ /// <param name="bytesWritten">The number of bytes successfully written</param>
+ /// <returns>true if any bytes where written, false if write failed</returns>
+ public bool WriteMemory(ulong address, Span<byte> buffer, out int bytesWritten)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ #endregion
+ }
+}
--- /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.Linq;
+using System;
+using System.Collections.Immutable;
+using System.IO;
+using Microsoft.Diagnostics.Runtime.Utilities;
+using System.Diagnostics;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Memory service wrapper that maps always module's metadata into the address
+ /// space even is some or all of the memory exists in the coredump. lldb returns
+ /// zero's (instead of failing the memory read) for missing pages in core dumps
+ /// that older (less than 5.0) createdumps generate so it needs this special
+ /// metadata mapping memory service.
+ /// </summary>
+ internal class MetadataMappingMemoryService : IMemoryService
+ {
+ private readonly IRuntime _runtime;
+ private readonly IMemoryService _memoryService;
+ private readonly ISymbolService _symbolService;
+ private bool _regionInitialized;
+ private ImmutableArray<MetadataRegion> _regions;
+
+ /// <summary>
+ /// Memory service constructor
+ /// </summary>
+ /// <param name="runtime">runtime instance</param>
+ /// <param name="memoryService">memory service to wrap</param>
+ /// <param name="symbolService">symbol service</param>
+ internal MetadataMappingMemoryService(IRuntime runtime, IMemoryService memoryService, ISymbolService symbolService)
+ {
+ _runtime = runtime;
+ _memoryService = memoryService;
+ _symbolService = symbolService;
+ }
+
+ /// <summary>
+ /// Flush the metadata memory service
+ /// </summary>
+ public void Flush()
+ {
+ _regionInitialized = false;
+ _regions.Clear();
+ }
+
+ #region IMemoryService
+
+ /// <summary>
+ /// Returns the pointer size of the target
+ /// </summary>
+ public int PointerSize => _memoryService.PointerSize;
+
+ /// <summary>
+ /// Read memory out of the target process.
+ /// </summary>
+ /// <param name="address">The address of memory to read</param>
+ /// <param name="buffer">The buffer to read memory into</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
+ public bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead)
+ {
+ Debug.Assert((address & ~_memoryService.SignExtensionMask()) == 0);
+ if (buffer.Length > 0)
+ {
+ MetadataRegion region = FindRegion(address);
+ if (region != null)
+ {
+ if (region.ReadMetaData(address, buffer, out bytesRead)) {
+ return true;
+ }
+ }
+ }
+ return _memoryService.ReadMemory(address, buffer, out bytesRead);
+ }
+
+ /// <summary>
+ /// Write memory into target process for supported targets.
+ /// </summary>
+ /// <param name="address">The address of memory to write</param>
+ /// <param name="buffer">The buffer to write</param>
+ /// <param name="bytesWritten">The number of bytes successfully written</param>
+ /// <returns>true if any bytes where written, false if write failed</returns>
+ public bool WriteMemory(ulong address, Span<byte> buffer, out int bytesWritten)
+ {
+ Debug.Assert((address & ~_memoryService.SignExtensionMask()) == 0);
+ return _memoryService.WriteMemory(address, buffer, out bytesWritten);
+ }
+
+ #endregion
+
+ private MetadataRegion FindRegion(ulong address)
+ {
+ if (!_regionInitialized)
+ {
+ _regionInitialized = true;
+
+ Trace.TraceInformation($"FindRegion: initializing regions for runtime #{_runtime.Id}");
+ ClrRuntime clrruntime = _runtime.Services.GetService<ClrRuntime>();
+ if (clrruntime != null)
+ {
+ _regions = clrruntime.EnumerateModules()
+ .Where((module) => module.MetadataAddress != 0 && module.IsPEFile && !module.IsDynamic)
+ .Select((module) => new MetadataRegion(this, module))
+ .ToImmutableArray()
+ .Sort();
+ }
+ }
+
+ if (_regions != null)
+ {
+ int min = 0, max = _regions.Length - 1;
+ while (min <= max)
+ {
+ int mid = (min + max) / 2;
+ MetadataRegion region = _regions[mid];
+
+ if (address >= region.StartAddress && address < region.EndAddress)
+ {
+ return region;
+ }
+
+ if (region.StartAddress < address)
+ {
+ min = mid + 1;
+ }
+ else
+ {
+ max = mid - 1;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private ImmutableArray<byte> GetMetaDataFromAssembly(ClrModule module)
+ {
+ Debug.Assert(module.ImageBase != 0);
+
+ var metadata = ImmutableArray<byte>.Empty;
+ bool isVirtual = module.Layout != ModuleLayout.Flat;
+ try
+ {
+ ulong size = module.Size;
+ if (size == 0) {
+ size = 4096;
+ }
+ Stream stream = _memoryService.CreateMemoryStream(module.ImageBase, size);
+ var peImage = new PEImage(stream, leaveOpen: false, isVirtual);
+ if (peImage.IsValid)
+ {
+ metadata = _symbolService.GetMetadata(module.Name, (uint)peImage.IndexTimeStamp, (uint)peImage.IndexFileSize);
+ }
+ else
+ {
+ Trace.TraceError($"GetMetaData: {module.ImageBase:X16} not valid PE");
+ }
+ }
+ catch (Exception ex) when (ex is BadImageFormatException || ex is EndOfStreamException || ex is IOException)
+ {
+ Trace.TraceError($"GetMetaData: loaded {module.ImageBase:X16} exception {ex.Message}");
+ }
+ return metadata;
+ }
+
+ class MetadataRegion : IComparable<MetadataRegion>
+ {
+ private readonly MetadataMappingMemoryService _memoryService;
+ private readonly ClrModule _module;
+
+ internal ulong StartAddress => _module.MetadataAddress;
+
+ internal ulong EndAddress => _module.MetadataAddress + _module.MetadataLength;
+
+ private ImmutableArray<byte> _metadata;
+
+ internal MetadataRegion(MetadataMappingMemoryService memoryService, ClrModule module)
+ {
+ _memoryService = memoryService;
+ _module = module;
+ }
+
+ public int CompareTo(MetadataRegion region)
+ {
+ return StartAddress.CompareTo(region.StartAddress);
+ }
+
+ internal bool ReadMetaData(ulong address, Span<byte> buffer, out int bytesRead)
+ {
+ ImmutableArray<byte> metadata = GetMetaData();
+ if (!metadata.IsDefaultOrEmpty)
+ {
+ bytesRead = Math.Min(buffer.Length, metadata.Length);
+ int offset = (int)(address - StartAddress);
+ metadata.AsSpan().Slice(offset, bytesRead).CopyTo(buffer);
+ return true;
+ }
+ bytesRead = 0;
+ return false;
+ }
+
+ private ImmutableArray<byte> GetMetaData()
+ {
+ if (_metadata.IsDefault)
+ {
+ _metadata = _memoryService.GetMetaDataFromAssembly(_module);
+ }
+ return _metadata;
+ }
+
+ public override string ToString()
+ {
+ return $"{StartAddress} {EndAddress} {_module.Name}";
+ }
+ }
+ }
+}
--- /dev/null
+<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <NoWarn>;1591;1701</NoWarn>
+ <Description>Diagnostics debug services</Description>
+ <IsPackable>true</IsPackable>
+ <PackageTags>Diagnostic</PackageTags>
+ <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <IncludeSymbols>true</IncludeSymbols>
+ <IsShippingAssembly>true</IsShippingAssembly>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="$(MicrosoftDiagnosticsRuntimeVersion)" />
+ <PackageReference Include="Microsoft.SymbolStore" Version="$(MicrosoftSymbolStoreVersion)" />
+ <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
+ <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\SOS\SOS.NETCore\SOS.NETCore.csproj" />
+ </ItemGroup>
+</Project>
--- /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 Microsoft.Diagnostics.Runtime.Utilities;
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.PortableExecutable;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Module base implementation
+ /// </summary>
+ public abstract class Module : IModule
+ {
+ [Flags]
+ public enum Flags : byte
+ {
+ None = 0x00,
+ IsPEImage = 0x01,
+ IsManaged = 0x02,
+ IsFileLayout = 0x04,
+ IsLoadedLayout = 0x08,
+ InitializePEInfo = 0x10,
+ InitializeVersion = 0x20,
+ InitializeProductVersion = 0x40,
+ }
+
+ private Flags _flags;
+ private PdbInfo _pdbInfo;
+ private ImmutableArray<byte> _buildId;
+ private VersionInfo? _version;
+ private PEImage _peImage;
+
+ public readonly ServiceProvider ServiceProvider;
+
+ public Module()
+ {
+ ServiceProvider = new ServiceProvider();
+ ServiceProvider.AddServiceFactoryWithNoCaching<PEImage>(() => GetPEInfo());
+ ServiceProvider.AddServiceFactory<PEReader>(() => {
+ // BUGBUG - this can leak OnChangeEvent handlers
+ ModuleService.SymbolService.OnChangeEvent += (object sender, EventArgs e) => ServiceProvider.RemoveService(typeof(PEReader));
+ return ModuleService.GetPEReader(this);
+ });
+ }
+
+ #region IModule
+
+ public IServiceProvider Services => ServiceProvider;
+
+ public abstract int ModuleIndex { get; }
+
+ public abstract string FileName { get; }
+
+ public abstract ulong ImageBase { get; }
+
+ public abstract ulong ImageSize { get; }
+
+ public abstract int IndexFileSize { get; }
+
+ public abstract int IndexTimeStamp { get; }
+
+ public bool IsPEImage
+ {
+ get
+ {
+ GetPEInfo();
+ return (_flags & Flags.IsPEImage) != 0;
+ }
+ }
+
+ public bool IsManaged
+ {
+ get
+ {
+ GetPEInfo();
+ return (_flags & Flags.IsManaged) != 0;
+ }
+ }
+
+ public bool? IsFileLayout
+ {
+ get
+ {
+ GetPEInfo();
+ if ((_flags & Flags.IsFileLayout) != 0)
+ {
+ return true;
+ }
+ if ((_flags & Flags.IsLoadedLayout) != 0)
+ {
+ return false;
+ }
+ return null;
+ }
+ }
+
+ public PdbInfo PdbInfo
+ {
+ get
+ {
+ GetPEInfo();
+ return _pdbInfo;
+ }
+ }
+
+ public ImmutableArray<byte> BuildId
+ {
+ get
+ {
+ if (_buildId.IsDefault)
+ {
+ byte[] id = ModuleService.GetBuildId(ImageBase, ImageSize);
+ if (id != null)
+ {
+ _buildId = id.ToImmutableArray();
+ }
+ else
+ {
+ _buildId = ImmutableArray<byte>.Empty;
+ }
+ }
+ return _buildId;
+ }
+ }
+
+ public virtual VersionInfo? Version
+ {
+ get { return _version; }
+ set { _version = value; }
+ }
+
+ public abstract string VersionString { get; }
+
+ #endregion
+
+ protected void GetVersionFromVersionString()
+ {
+ GetPEInfo();
+
+ // If we can't get the version from the PE, search for version string embedded in the module data
+ if (!_version.HasValue && !IsPEImage)
+ {
+ string versionString = VersionString;
+ if (versionString != null)
+ {
+ int spaceIndex = versionString.IndexOf(' ');
+ if (spaceIndex > 0)
+ {
+ if (versionString[spaceIndex - 1] == '.')
+ {
+ spaceIndex--;
+ }
+ string versionToParse = versionString.Substring(0, spaceIndex);
+ try
+ {
+ Version version = System.Version.Parse(versionToParse);
+ _version = new VersionInfo(version.Major, version.Minor, version.Build, version.Revision);
+ }
+ catch (ArgumentException ex)
+ {
+ Trace.TraceError($"Module.Version FAILURE: '{versionToParse}' '{versionString}' {ex}");
+ }
+ }
+ }
+ }
+ }
+
+ protected PEImage GetPEInfo()
+ {
+ if (InitializeValue(Flags.InitializePEInfo)) {
+ _peImage = ModuleService.GetPEInfo(ImageBase, ImageSize, ref _pdbInfo, ref _version, ref _flags);
+ }
+ return _peImage;
+ }
+
+ protected bool InitializeValue(Flags flag)
+ {
+ if ((_flags & flag) == 0)
+ {
+ _flags |= flag;
+ return true;
+ }
+ return false;
+ }
+
+ protected abstract ModuleService ModuleService { get; }
+
+ public override string ToString()
+ {
+ return $"#{ModuleIndex} {ImageBase:X16} {_flags} {FileName ?? ""}";
+ }
+ }
+}
--- /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 Microsoft.Diagnostics.Runtime.Utilities;
+using Microsoft.FileFormats;
+using Microsoft.FileFormats.ELF;
+using Microsoft.FileFormats.MachO;
+using Microsoft.SymbolStore;
+using Microsoft.SymbolStore.KeyGenerators;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection.PortableExecutable;
+using System.Runtime.InteropServices;
+using System.Text;
+using FileVersionInfo = Microsoft.Diagnostics.Runtime.Utilities.FileVersionInfo;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Module service base implementation
+ /// </summary>
+ public abstract class ModuleService : IModuleService
+ {
+ [Flags]
+ internal enum ELFProgramHeaderAttributes : uint
+ {
+ Executable = 1, // PF_X
+ Writable = 2, // PF_W
+ Readable = 4, // PF_R
+ OSMask = 0x0FF00000, // PF_MASKOS
+ ProcessorMask = 0xF0000000, // PF_MASKPROC
+ }
+
+ // MachO writable segment attribute
+ const uint VmProtWrite = 0x02;
+
+ protected readonly ITarget Target;
+ private IMemoryService _memoryService;
+ private ISymbolService _symbolService;
+ private ReadVirtualCache _versionCache;
+ private Dictionary<ulong, IModule> _modules;
+ private IModule[] _sortedByBaseAddress;
+
+ private static readonly byte[] s_versionString = Encoding.ASCII.GetBytes("@(#)Version ");
+ private static readonly int s_versionLength = s_versionString.Length;
+
+ public ModuleService(ITarget target)
+ {
+ Debug.Assert(target != null);
+ Target = target;
+
+ target.OnFlushEvent += (object sender, EventArgs e) => {
+ _versionCache?.Clear();
+ _modules = null;
+ _sortedByBaseAddress = null;
+ };
+ }
+
+ #region IModuleService
+
+ /// <summary>
+ /// Enumerate all the modules in the target
+ /// </summary>
+ IEnumerable<IModule> IModuleService.EnumerateModules()
+ {
+ GetModules();
+ return _sortedByBaseAddress;
+ }
+
+ /// <summary>
+ /// Get the module info from the module index
+ /// </summary>
+ /// <param name="moduleIndex">index</param>
+ /// <returns>module</returns>
+ /// <exception cref="DiagnosticsException">invalid module index</exception>
+ IModule IModuleService.GetModuleFromIndex(int moduleIndex)
+ {
+ try
+ {
+ return GetModules().First((pair) => pair.Value.ModuleIndex == moduleIndex).Value;
+ }
+ catch (ArgumentOutOfRangeException ex)
+ {
+ throw new DiagnosticsException($"Invalid module index: {moduleIndex}", ex);
+ }
+ }
+
+ /// <summary>
+ /// Get the module info from the module base address
+ /// </summary>
+ /// <param name="baseAddress"></param>
+ /// <returns>module</returns>
+ /// <exception cref="DiagnosticsException">base address not found</exception>
+ IModule IModuleService.GetModuleFromBaseAddress(ulong baseAddress)
+ {
+ if (!GetModules().TryGetValue(baseAddress, out IModule module)) {
+ throw new DiagnosticsException($"Invalid module base address: {baseAddress:X16}");
+ }
+ return module;
+ }
+
+ /// <summary>
+ /// Finds the module that contains the address.
+ /// </summary>
+ /// <param name="address">search address</param>
+ /// <returns>module or null</returns>
+ IModule IModuleService.GetModuleFromAddress(ulong address)
+ {
+ GetModules();
+
+ Debug.Assert((address & ~MemoryService.SignExtensionMask()) == 0);
+ int min = 0, max = _sortedByBaseAddress.Length - 1;
+
+ // Check if there is a module that contains the address range
+ while (min <= max)
+ {
+ int mid = (min + max) / 2;
+ IModule module = _sortedByBaseAddress[mid];
+
+ ulong start = module.ImageBase;
+ Debug.Assert((start & ~MemoryService.SignExtensionMask()) == 0);
+ ulong end = start + module.ImageSize;
+
+ if (address >= start && address < end) {
+ return module;
+ }
+
+ if (module.ImageBase < address) {
+ min = mid + 1;
+ }
+ else {
+ max = mid - 1;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Finds the module(s) with the specified module name. It is the platform dependent
+ /// name that includes the "lib" prefix on xplat and the extension (dll, so or dylib).
+ /// </summary>
+ /// <param name="moduleName">module name to find</param>
+ /// <returns>matching modules</returns>
+ IEnumerable<IModule> IModuleService.GetModuleFromModuleName(string moduleName)
+ {
+ moduleName = Path.GetFileName(moduleName);
+ foreach (IModule module in GetModules().Values)
+ {
+ if (IsModuleEqual(module, moduleName))
+ {
+ yield return module;
+ }
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Get/create the modules dictionary and sorted array.
+ /// </summary>
+ private Dictionary<ulong, IModule> GetModules()
+ {
+ if (_modules == null)
+ {
+ _modules = GetModulesInner();
+ _sortedByBaseAddress = _modules.OrderBy((pair) => pair.Key).Select((pair) => pair.Value).ToArray();
+ }
+ return _modules;
+ }
+
+ /// <summary>
+ /// Get/create the modules.
+ /// </summary>
+ protected abstract Dictionary<ulong, IModule> GetModulesInner();
+
+ /// <summary>
+ /// Returns the PE file's PDB info from the debug directory
+ /// </summary>
+ /// <param name="address">module base address</param>
+ /// <param name="size">module size</param>
+ /// <param name="pdbInfo">the pdb record or null</param>
+ /// <param name="version">the PE version or null</param>
+ /// <param name="flags">module flags</param>
+ /// <returns>PEImage instance or null</returns>
+ internal PEImage GetPEInfo(ulong address, ulong size, ref PdbInfo pdbInfo, ref VersionInfo? version, ref Module.Flags flags)
+ {
+ PEImage peImage = null;
+
+ // None of the modules that lldb (on either Linux/MacOS) provides are PEs
+ if (Target.Host.HostType != HostType.Lldb)
+ {
+ // First try getting the PE info as load layout (native Windows DLLs and most managed PEs on Linux/MacOS).
+ peImage = GetPEInfo(isVirtual: true, address, size, ref pdbInfo, ref version, ref flags);
+ if (peImage == null)
+ {
+ if (Target.OperatingSystem != OSPlatform.Windows)
+ {
+ // Then try getting the PE info as file layout (some managed PEs on Linux/MacOS).
+ peImage = GetPEInfo(isVirtual: false, address, size, ref pdbInfo, ref version, ref flags);
+ }
+ }
+ }
+ return peImage;
+ }
+
+ /// <summary>
+ /// Returns the PE file's PDB info from the debug directory
+ /// </summary>
+ /// <param name="isVirtual">the memory layout of the module</param>
+ /// <param name="address">module base address</param>
+ /// <param name="size">module size</param>
+ /// <param name="pdbInfo">the pdb record or null</param>
+ /// <param name="version">the PE version or null</param>
+ /// <param name="flags">module flags</param>
+ /// <returns>PEImage instance or null</returns>
+ private PEImage GetPEInfo(bool isVirtual, ulong address, ulong size, ref PdbInfo pdbInfo, ref VersionInfo? version, ref Module.Flags flags)
+ {
+ Stream stream = MemoryService.CreateMemoryStream(address, size);
+ try
+ {
+ stream.Position = 0;
+ var peImage = new PEImage(stream, leaveOpen: false, isVirtual);
+ if (peImage.IsValid)
+ {
+ flags |= Module.Flags.IsPEImage;
+ flags |= peImage.IsManaged ? Module.Flags.IsManaged : Module.Flags.None;
+ pdbInfo = peImage.DefaultPdb;
+ if (!version.HasValue)
+ {
+ FileVersionInfo fileVersionInfo = peImage.GetFileVersionInfo();
+ if (fileVersionInfo != null)
+ {
+ version = fileVersionInfo.VersionInfo;
+ }
+ }
+ flags &= ~(Module.Flags.IsLoadedLayout | Module.Flags.IsFileLayout);
+ flags |= isVirtual ? Module.Flags.IsLoadedLayout : Module.Flags.IsFileLayout;
+ return peImage;
+ }
+ }
+ catch (Exception ex) when (ex is BadImageFormatException || ex is EndOfStreamException || ex is IOException)
+ {
+ Trace.TraceError($"GetPEInfo: loaded {address:X16} exception {ex.Message}");
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Finds or downloads the module and creates a PEReader for it.
+ /// </summary>
+ /// <param name="module">module instance</param>
+ /// <returns>reader or null</returns>
+ internal PEReader GetPEReader(IModule module)
+ {
+ string downloadFilePath = null;
+ PEReader reader = null;
+
+ if (File.Exists(module.FileName))
+ {
+ downloadFilePath = module.FileName;
+ }
+ else
+ {
+ if (SymbolService.IsSymbolStoreEnabled)
+ {
+ SymbolStoreKey key = PEFileKeyGenerator.GetKey(Path.GetFileName(module.FileName), (uint)module.IndexTimeStamp, (uint)module.IndexFileSize);
+ if (key != null)
+ {
+ // Now download the module from the symbol server
+ downloadFilePath = SymbolService.DownloadFile(key);
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(downloadFilePath))
+ {
+ Trace.TraceInformation("GetPEReader: downloading {0}", downloadFilePath);
+ Stream stream = null;
+ try
+ {
+ stream = File.OpenRead(downloadFilePath);
+ }
+ catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException || ex is UnauthorizedAccessException || ex is IOException)
+ {
+ Trace.TraceError($"GetPEReader: exception {ex.Message}");
+ }
+ if (stream != null)
+ {
+ try
+ {
+ reader = new PEReader(stream);
+ if (reader.PEHeaders == null || reader.PEHeaders.PEHeader == null)
+ {
+ reader = null;
+ }
+ }
+ catch (Exception ex) when (ex is BadImageFormatException || ex is IOException)
+ {
+ Trace.TraceError($"GetPEReader: exception {ex.Message}");
+ reader = null;
+ }
+ }
+ }
+ return reader;
+ }
+
+ /// <summary>
+ /// Returns the ELF module build id or the MachO module uuid
+ /// </summary>
+ /// <param name="address">module base address</param>
+ /// <param name="size">module size</param>
+ /// <returns>build id or null</returns>
+ internal byte[] GetBuildId(ulong address, ulong size)
+ {
+ Debug.Assert(size > 0);
+ Stream stream = MemoryService.CreateMemoryStream(address, size);
+ byte[] buildId = null;
+ try
+ {
+ if (Target.OperatingSystem == OSPlatform.Linux)
+ {
+ var elfFile = new ELFFile(new StreamAddressSpace(stream), 0, true);
+ if (elfFile.IsValid())
+ {
+ buildId = elfFile.BuildID;
+ }
+ }
+ else if (Target.OperatingSystem == OSPlatform.OSX)
+ {
+ var machOFile = new MachOFile(new StreamAddressSpace(stream), 0, true);
+ if (machOFile.IsValid())
+ {
+ buildId = machOFile.Uuid;
+ }
+ }
+ }
+ catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+ {
+ Trace.TraceError($"GetBuildId: {address:X16} exception {ex.Message}");
+ }
+ return buildId;
+ }
+
+ /// <summary>
+ /// Get the version string from a Linux or MacOS image
+ /// </summary>
+ /// <param name="address">image base</param>
+ /// <param name="size">image size</param>
+ /// <returns>version string or null</returns>
+ protected string GetVersionString(ulong address, ulong size)
+ {
+ Stream stream = MemoryService.CreateMemoryStream(address, size);
+ try
+ {
+ if (Target.OperatingSystem == OSPlatform.Linux)
+ {
+ var elfFile = new ELFFile(new StreamAddressSpace(stream), 0, true);
+ if (elfFile.IsValid())
+ {
+ foreach (ELFProgramHeader programHeader in elfFile.Segments.Select((segment) => segment.Header))
+ {
+ uint flags = MemoryService.PointerSize == 8 ? programHeader.Flags : programHeader.Flags32;
+ if (programHeader.Type == ELFProgramHeaderType.Load &&
+ (flags & (uint)ELFProgramHeaderAttributes.Writable) != 0)
+ {
+ ulong loadAddress = programHeader.VirtualAddress.Value;
+ long loadSize = (long)programHeader.VirtualSize;
+ if (SearchVersionString(address + loadAddress, loadSize, out string productVersion))
+ {
+ return productVersion;
+ }
+ }
+ }
+ }
+ }
+ else if (Target.OperatingSystem == OSPlatform.OSX)
+ {
+ var machOFile = new MachOFile(new StreamAddressSpace(stream), 0, true);
+ if (machOFile.IsValid())
+ {
+ foreach (MachSegmentLoadCommand loadCommand in machOFile.Segments.Select((segment) => segment.LoadCommand))
+ {
+ if (loadCommand.Command == LoadCommandType.Segment64 &&
+ (loadCommand.InitProt & VmProtWrite) != 0 &&
+ loadCommand.SegName.ToString() != "__LINKEDIT")
+ {
+ ulong loadAddress = loadCommand.VMAddress;
+ long loadSize = (long)loadCommand.VMSize;
+ if (SearchVersionString(address + loadAddress, loadSize, out string productVersion))
+ {
+ return productVersion;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ Trace.TraceError("GetVersionString: unsupported platform {0}", Target.OperatingSystem);
+ }
+ }
+ catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+ {
+ Trace.TraceError($"GetVersionString: {address:X16} exception {ex.Message}");
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Linux/MacOS version string search helper
+ /// </summary>
+ /// <param name="address">beginning of module memory</param>
+ /// <param name="size">size of module</param>
+ /// <param name="fileVersion">returned version string</param>
+ /// <returns>true if successful</returns>
+ private bool SearchVersionString(ulong address, long size, out string fileVersion)
+ {
+ byte[] buffer = new byte[s_versionString.Length];
+
+ if (_versionCache == null) {
+ _versionCache = new ReadVirtualCache(MemoryService);
+ }
+ _versionCache.Clear();
+
+ while (size > 0)
+ {
+ bool result = _versionCache.Read(address, buffer, s_versionString.Length, out int cbBytesRead);
+ if (result && cbBytesRead >= s_versionLength)
+ {
+ if (s_versionString.SequenceEqual(buffer))
+ {
+ address += (ulong)s_versionLength;
+ size -= s_versionLength;
+
+ var sb = new StringBuilder();
+ byte[] ch = new byte[1];
+ while (true)
+ {
+ // Now read the version string a char/byte at a time
+ result = _versionCache.Read(address, ch, ch.Length, out cbBytesRead);
+
+ // Return not found if there are any failures or problems while reading the version string.
+ if (!result || cbBytesRead < ch.Length || size <= 0)
+ {
+ break;
+ }
+
+ // Found the end of the string
+ if (ch[0] == '\0')
+ {
+ fileVersion = sb.ToString();
+ return true;
+ }
+ sb.Append(Encoding.ASCII.GetChars(ch));
+ address++;
+ size--;
+ }
+ // Return not found if overflowed the fileVersionBuffer (not finding a null).
+ break;
+ }
+ address++;
+ size--;
+ }
+ else
+ {
+ address += (ulong)s_versionLength;
+ size -= s_versionLength;
+ }
+ }
+
+ fileVersion = null;
+ return false;
+ }
+
+ private bool IsModuleEqual(IModule module, string moduleName)
+ {
+ if (Target.OperatingSystem == OSPlatform.Windows) {
+ return StringComparer.OrdinalIgnoreCase.Equals(Path.GetFileName(module.FileName), moduleName);
+ }
+ else {
+ return string.Equals(Path.GetFileName(module.FileName), moduleName);
+ }
+ }
+
+ protected IMemoryService MemoryService
+ {
+ get
+ {
+ if (_memoryService == null) {
+ _memoryService = Target.Services.GetService<IMemoryService>();
+ }
+ return _memoryService;
+ }
+ }
+
+ internal ISymbolService SymbolService
+ {
+ get
+ {
+ if (_symbolService == null) {
+ _symbolService = Target.Services.GetService<ISymbolService>();
+ }
+ return _symbolService;
+ }
+ }
+
+ /// <summary>
+ /// Search memory helper class
+ /// </summary>
+ internal class ReadVirtualCache
+ {
+ private const int CACHE_SIZE = 4096;
+
+ private readonly IMemoryService _memoryService;
+ private readonly byte[] _cache = new byte[CACHE_SIZE];
+ private ulong _startCache;
+ private bool _cacheValid;
+ private int _cacheSize;
+
+ internal ReadVirtualCache(IMemoryService memoryService)
+ {
+ _memoryService = memoryService;
+ Clear();
+ }
+
+ internal bool Read(ulong address, byte[] buffer, int bufferSize, out int bytesRead)
+ {
+ bytesRead = 0;
+
+ if (bufferSize == 0)
+ {
+ return true;
+ }
+
+ if (bufferSize > CACHE_SIZE)
+ {
+ // Don't even try with the cache
+ return _memoryService.ReadMemory(address, buffer, bufferSize, out bytesRead);
+ }
+
+ if (!_cacheValid || (address < _startCache) || (address > (_startCache + (ulong)(_cacheSize - bufferSize))))
+ {
+ _cacheValid = false;
+ _startCache = address;
+ if (!_memoryService.ReadMemory(_startCache, _cache, _cache.Length, out int cbBytesRead))
+ {
+ return false;
+ }
+ _cacheSize = cbBytesRead;
+ _cacheValid = true;
+ }
+
+ int size = Math.Min(bufferSize, _cacheSize);
+ Array.Copy(_cache, (int)(address - _startCache), buffer, 0, size);
+ bytesRead = size;
+ return true;
+ }
+
+ internal void Clear()
+ {
+ _cacheValid = false;
+ _cacheSize = CACHE_SIZE;
+ }
+ }
+ }
+}
--- /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.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Module service implementation for the clrmd IDataReader
+ /// </summary>
+ public class ModuleServiceFromDataReader : ModuleService
+ {
+ class ModuleFromDataReader : Module
+ {
+ private static readonly VersionInfo EmptyVersionInfo = new VersionInfo(0, 0, 0, 0);
+ private readonly ModuleServiceFromDataReader _moduleService;
+ private readonly ModuleInfo _moduleInfo;
+ private string _versionString;
+
+ public ModuleFromDataReader(ModuleServiceFromDataReader moduleService, int moduleIndex, ModuleInfo moduleInfo)
+ {
+ _moduleService = moduleService;
+ _moduleInfo = moduleInfo;
+ ModuleIndex = moduleIndex;
+ }
+
+ #region IModule
+
+ public override int ModuleIndex { get; }
+
+ public override string FileName => _moduleInfo.FileName;
+
+ public override ulong ImageBase => _moduleInfo.ImageBase;
+
+ public override ulong ImageSize => (uint)_moduleInfo.IndexFileSize;
+
+ public override int IndexFileSize => _moduleInfo.IndexFileSize;
+
+ public override int IndexTimeStamp => _moduleInfo.IndexTimeStamp;
+
+ public override VersionInfo? Version
+ {
+ get
+ {
+ if (InitializeValue(Module.Flags.InitializeVersion))
+ {
+ if (_moduleInfo.Version != EmptyVersionInfo)
+ {
+ base.Version = _moduleInfo.Version;
+ }
+ else
+ {
+ if (_moduleService.Target.OperatingSystem != OSPlatform.Windows)
+ {
+ GetVersionFromVersionString();
+ }
+ }
+ }
+ return base.Version;
+ }
+ }
+
+ public override string VersionString
+ {
+ get
+ {
+ if (InitializeValue(Module.Flags.InitializeProductVersion))
+ {
+ if (_moduleService.Target.OperatingSystem != OSPlatform.Windows && !IsPEImage)
+ {
+ _versionString = _moduleService.GetVersionString(ImageBase, ImageSize);
+ }
+ }
+ return _versionString;
+ }
+ }
+
+ #endregion
+
+ protected override ModuleService ModuleService => _moduleService;
+ }
+
+ private readonly IDataReader _dataReader;
+
+ public ModuleServiceFromDataReader(ITarget target, IDataReader dataReader)
+ : base(target)
+ {
+ _dataReader = dataReader;
+ }
+
+ /// <summary>
+ /// Get/create the modules dictionary.
+ /// </summary>
+ protected override Dictionary<ulong, IModule> GetModulesInner()
+ {
+ var modules = new Dictionary<ulong, IModule>();
+ int moduleIndex = 0;
+
+ foreach (ModuleInfo moduleInfo in _dataReader.EnumerateModules().OrderBy((info) => info.ImageBase))
+ {
+ var module = new ModuleFromDataReader(this, moduleIndex, moduleInfo);
+ if (!modules.TryGetValue(moduleInfo.ImageBase, out IModule dup))
+ {
+ modules.Add(moduleInfo.ImageBase, module);
+ }
+ else
+ {
+ Trace.TraceError($"GetModules(): duplicate module base '{module}' dup '{dup}'");
+ }
+ 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.Diagnostics;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Memory service wrapper that maps and fixes up PE module on read memory errors.
+ /// </summary>
+ internal class PEImageMappingMemoryService : IMemoryService
+ {
+ private readonly IMemoryService _memoryService;
+ private readonly IModuleService _moduleService;
+ private readonly MemoryCache _memoryCache;
+
+ /// <summary>
+ /// Memory service constructor
+ /// </summary>
+ /// <param name="target"></param>
+ /// <param name="memoryService">memory service to wrap</param>
+ internal PEImageMappingMemoryService(ITarget target, IMemoryService memoryService)
+ {
+ _memoryService = memoryService;
+ _moduleService = target.Services.GetService<IModuleService>();
+ _memoryCache = new MemoryCache(ReadMemoryFromModule);
+
+ target.OnFlushEvent += (object sender, EventArgs e) => {
+ _memoryCache.FlushCache();
+ };
+ }
+
+ #region IMemoryService
+
+ /// <summary>
+ /// Returns the pointer size of the target
+ /// </summary>
+ public int PointerSize => _memoryService.PointerSize;
+
+ /// <summary>
+ /// Read memory out of the target process.
+ /// </summary>
+ /// <param name="address">The address of memory to read</param>
+ /// <param name="buffer">The buffer to read memory into</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
+ public bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead)
+ {
+ int bytesRequested = buffer.Length;
+ _memoryService.ReadMemory(address, buffer, out bytesRead);
+
+ // If the read failed or a successful partial read
+ if (bytesRequested != bytesRead)
+ {
+ // Check if the memory is in a module and cache it if it is
+ if (_memoryCache.ReadMemory(address + (uint)bytesRead, buffer.Slice(bytesRead), out int read))
+ {
+ bytesRead += read;
+ }
+ }
+ return bytesRead > 0;
+ }
+
+ /// <summary>
+ /// Write memory into target process for supported targets.
+ /// </summary>
+ /// <param name="address">The address of memory to write</param>
+ /// <param name="buffer">The buffer to write</param>
+ /// <param name="bytesWritten">The number of bytes successfully written</param>
+ /// <returns>true if any bytes where written, false if write failed</returns>
+ public bool WriteMemory(ulong address, Span<byte> buffer, out int bytesWritten)
+ {
+ return _memoryService.WriteMemory(address, buffer, out bytesWritten);
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Read memory from a PE module for the memory cache. Finds locally or downloads a module
+ /// and "maps" it into the address space. This function can return more than requested which
+ /// means the block should not be cached.
+ /// </summary>
+ /// <param name="address">memory address</param>
+ /// <param name="bytesRequested">number of bytes</param>
+ /// <returns>bytes read or null if error</returns>
+ private byte[] ReadMemoryFromModule(ulong address, int bytesRequested)
+ {
+ Debug.Assert((address & ~_memoryService.SignExtensionMask()) == 0);
+ IModule module = _moduleService.GetModuleFromAddress(address);
+ if (module != null)
+ {
+ Trace.TraceInformation("ReadMemory: address {0:X16} size {1:X8} found module {2}", address, bytesRequested, module.FileName);
+
+ // We found a module that contains the memory requested. Now find or download the PE image.
+ PEReader reader = module.Services.GetService<PEReader>();
+ if (reader != null)
+ {
+ // Read the memory from the PE image.
+ int rva = (int)(address - module.ImageBase);
+ Debug.Assert(rva >= 0);
+ try
+ {
+ byte[] data = null;
+
+ int sizeOfHeaders = reader.PEHeaders.PEHeader.SizeOfHeaders;
+ if (rva < sizeOfHeaders)
+ {
+ // If the address isn't contained in one of the sections, assume that SOS is reading the PE headers directly.
+ Trace.TraceInformation("ReadMemory: rva {0:X8} size {1:X8} in PE Header", rva, bytesRequested);
+ data = reader.GetEntireImage().GetReader(rva, bytesRequested).ReadBytes(bytesRequested);
+ }
+ else
+ {
+ PEMemoryBlock block = reader.GetSectionData(rva);
+ if (block.Length > 0)
+ {
+ int size = Math.Min(block.Length, bytesRequested);
+ data = block.GetReader().ReadBytes(size);
+ ApplyRelocations(module, reader, rva, data);
+ }
+ }
+
+ return data;
+ }
+ catch (Exception ex) when (ex is BadImageFormatException || ex is InvalidOperationException || ex is IOException)
+ {
+ Trace.TraceError($"ReadMemory: exception {ex.Message}");
+ }
+ }
+ }
+ return null;
+ }
+
+ enum BaseRelocationType
+ {
+ ImageRelBasedAbsolute = 0,
+ ImageRelBasedHigh = 1,
+ ImageRelBasedLow = 2,
+ ImageRelBasedHighLow = 3,
+ ImageRelBasedHighAdj = 4,
+ ImageRelBasedDir64 = 10,
+ }
+
+ private void ApplyRelocations(IModule module, PEReader reader, int dataVA, byte[] data)
+ {
+ PEMemoryBlock relocations = reader.GetSectionData(".reloc");
+ if (relocations.Length > 0)
+ {
+ ulong baseDelta = module.ImageBase - reader.PEHeaders.PEHeader.ImageBase;
+#if TRACE_VERBOSE
+ Trace.TraceInformation("ApplyRelocations: dataVA {0:X8} dataCB {1} baseDelta: {2:X16}", dataVA, data.Length, baseDelta);
+#endif
+ BlobReader blob = relocations.GetReader();
+ while (blob.RemainingBytes > 0)
+ {
+ // Read IMAGE_BASE_RELOCATION struct
+ int virtualAddress = blob.ReadInt32();
+ int sizeOfBlock = blob.ReadInt32();
+
+ // Each relocation block covers 4K
+ if (dataVA >= virtualAddress && dataVA < (virtualAddress + 4096))
+ {
+ int entryCount = (sizeOfBlock - 8) / 2; // (sizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD)
+#if TRACE_VERBOSE
+ Trace.TraceInformation("ApplyRelocations: reloc VirtualAddress {0:X8} SizeOfBlock {1:X8} entry count {2}", virtualAddress, sizeOfBlock, entryCount);
+#endif
+ int relocsApplied = 0;
+ for (int e = 0; e < entryCount; e++)
+ {
+ // Read relocation type/offset
+ ushort entry = blob.ReadUInt16();
+ if (entry == 0) {
+ break;
+ }
+ var type = (BaseRelocationType)(entry >> 12); // type is 4 upper bits
+ int relocVA = virtualAddress + (entry & 0xfff); // offset is 12 lower bits
+
+ // Is this relocation in the data?
+ if (relocVA >= dataVA && relocVA < (dataVA + data.Length))
+ {
+ int offset = relocVA - dataVA;
+ switch (type)
+ {
+ case BaseRelocationType.ImageRelBasedAbsolute:
+ break;
+
+ case BaseRelocationType.ImageRelBasedHighLow:
+ {
+ uint value = BitConverter.ToUInt32(data, offset);
+ value += (uint)baseDelta;
+ byte[] source = BitConverter.GetBytes(value);
+ Array.Copy(source, 0, data, offset, source.Length);
+ break;
+ }
+ case BaseRelocationType.ImageRelBasedDir64:
+ {
+ ulong value = BitConverter.ToUInt64(data, offset);
+ value += baseDelta;
+ byte[] source = BitConverter.GetBytes(value);
+ Array.Copy(source, 0, data, offset, source.Length);
+ break;
+ }
+ default:
+ Debug.Fail($"ApplyRelocations: invalid relocation type {type}");
+ break;
+ }
+ relocsApplied++;
+ }
+ }
+#if TRACE_VERBOSE
+ Trace.TraceInformation("ApplyRelocations: relocs {0} applied", relocsApplied);
+#endif
+ }
+ else
+ {
+ // Skip to the next relocation block
+ blob.Offset += sizeOfBlock - 8;
+ }
+ }
+ }
+ }
+ }
+}
--- /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 Microsoft.SymbolStore;
+using Microsoft.SymbolStore.KeyGenerators;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// IRuntime instance implementation
+ /// </summary>
+ public class Runtime : IRuntime
+ {
+ private readonly ITarget _target;
+ private readonly IRuntimeService _runtimeService;
+ private readonly ClrInfo _clrInfo;
+ private IMemoryService _memoryService;
+ private ISymbolService _symbolService;
+ private MetadataMappingMemoryService _metadataMappingMemoryService;
+ private ClrRuntime _clrRuntime;
+ private string _dacFilePath;
+ private string _dbiFilePath;
+
+ public readonly ServiceProvider ServiceProvider;
+
+ public Runtime(ITarget target, IRuntimeService runtimeService, ClrInfo clrInfo, int id)
+ {
+ Trace.TraceInformation($"Creating runtime #{id} {clrInfo.Flavor} {clrInfo}");
+ _target = target;
+ _runtimeService = runtimeService;
+ _clrInfo = clrInfo;
+ Id = id;
+
+ RuntimeType = RuntimeType.Unknown;
+ if (clrInfo.Flavor == ClrFlavor.Core) {
+ RuntimeType = RuntimeType.NetCore;
+ }
+ else if (clrInfo.Flavor == ClrFlavor.Desktop) {
+ RuntimeType = RuntimeType.Desktop;
+ }
+ RuntimeModule = target.Services.GetService<IModuleService>().GetModuleFromBaseAddress(clrInfo.ModuleInfo.ImageBase);
+
+ ServiceProvider = new ServiceProvider();
+ ServiceProvider.AddService<ClrInfo>(clrInfo);
+ ServiceProvider.AddServiceFactoryWithNoCaching<ClrRuntime>(() => CreateRuntime());
+
+ // 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 (less than 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).
+ if (_target.IsDump &&
+ (_target.OperatingSystem != OSPlatform.Windows) &&
+ (_target.Host.HostType == HostType.Lldb ||
+ _target.Host.HostType == HostType.DotnetDump))
+ {
+ ServiceProvider.AddServiceFactoryWithNoCaching<IMemoryService>(() => {
+ if (_metadataMappingMemoryService == null)
+ {
+ _metadataMappingMemoryService = new MetadataMappingMemoryService(this, MemoryService, SymbolService);
+ SymbolService.OnChangeEvent += (object sender, EventArgs e) => _metadataMappingMemoryService?.Flush();
+ }
+ return _metadataMappingMemoryService;
+ });
+ }
+ }
+
+ /// <summary>
+ /// Flush the runtime instance
+ /// </summary>
+ public void Flush()
+ {
+ _clrRuntime?.DacLibrary.DacPrivateInterface.Flush();
+ _metadataMappingMemoryService?.Flush();
+ }
+
+ #region IRuntime
+
+ public IServiceProvider Services => ServiceProvider;
+
+ public int Id { get; }
+
+ public RuntimeType RuntimeType { get; }
+
+ public IModule RuntimeModule { get; }
+
+ public string GetDacFilePath()
+ {
+ if (_dacFilePath == null)
+ {
+ string dacFileName = GetDacFileName();
+ _dacFilePath = GetLocalDacPath(dacFileName);
+ if (_dacFilePath == null)
+ {
+ _dacFilePath = DownloadFile(dacFileName);
+ }
+ }
+ return _dacFilePath;
+ }
+
+ public string GetDbiFilePath()
+ {
+ if (_dbiFilePath == null)
+ {
+ string dbiFileName = GetDbiFileName();
+ _dbiFilePath = GetLocalPath(dbiFileName);
+ if (_dbiFilePath == null)
+ {
+ _dbiFilePath = DownloadFile(dbiFileName);
+ }
+ }
+ return _dbiFilePath;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Create ClrRuntime helper
+ /// </summary>
+ private ClrRuntime CreateRuntime()
+ {
+ if (_clrRuntime == null)
+ {
+ string dacFilePath = GetDacFilePath();
+ if (dacFilePath != null)
+ {
+ 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());
+ }
+ }
+ else
+ {
+ Trace.TraceError($"Could not find or download matching DAC for this runtime: {RuntimeModule.FileName}");
+ }
+ }
+ return _clrRuntime;
+ }
+
+ private string GetDacFileName()
+ {
+ Debug.Assert(!string.IsNullOrEmpty(_clrInfo.DacInfo.PlatformSpecificFileName));
+ string name = _clrInfo.DacInfo.PlatformSpecificFileName;
+
+ // If this is the Linux runtime module name, but we are running on Windows return the cross-OS DAC name.
+ if (_target.OperatingSystem == OSPlatform.Linux && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ name = "mscordaccore.dll";
+ }
+ return name;
+ }
+
+ private string GetLocalDacPath(string dacFileName)
+ {
+ string dacFilePath;
+ if (!string.IsNullOrEmpty(_runtimeService.RuntimeModuleDirectory))
+ {
+ dacFilePath = Path.Combine(_runtimeService.RuntimeModuleDirectory, dacFileName);
+ }
+ else
+ {
+ dacFilePath = _clrInfo.DacInfo.LocalDacPath;
+
+ // On MacOS CLRMD doesn't return the full DAC path just the file name so check if it exists
+ if (string.IsNullOrEmpty(dacFilePath) || !File.Exists(dacFilePath))
+ {
+ dacFilePath = Path.Combine(Path.GetDirectoryName(RuntimeModule.FileName), dacFileName);
+ }
+ }
+ if (!File.Exists(dacFilePath))
+ {
+ dacFilePath = null;
+ }
+ return dacFilePath;
+ }
+
+ private string GetDbiFileName()
+ {
+ string name = _target.GetPlatformModuleName("mscordbi");
+
+ // If this is the Linux runtime module name, but we are running on Windows return the cross-OS DBI name.
+ if (_target.OperatingSystem == OSPlatform.Linux && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ name = "mscordbi.dll";
+ }
+ return name;
+ }
+
+ private string GetLocalPath(string fileName)
+ {
+ string localFilePath;
+ if (!string.IsNullOrEmpty(_runtimeService.RuntimeModuleDirectory))
+ {
+ localFilePath = Path.Combine(_runtimeService.RuntimeModuleDirectory, fileName);
+ }
+ else
+ {
+ localFilePath = Path.Combine(Path.GetDirectoryName(RuntimeModule.FileName), fileName);
+ }
+ if (!File.Exists(localFilePath))
+ {
+ localFilePath = null;
+ }
+ return localFilePath;
+ }
+
+ private string DownloadFile(string fileName)
+ {
+ OSPlatform platform = _target.OperatingSystem;
+ string filePath = null;
+
+ if (SymbolService.IsSymbolStoreEnabled)
+ {
+ SymbolStoreKey key = null;
+
+ if (platform == OSPlatform.OSX)
+ {
+ KeyGenerator generator = MemoryService.GetKeyGenerator(
+ platform,
+ RuntimeModule.FileName,
+ RuntimeModule.ImageBase,
+ RuntimeModule.ImageSize);
+
+ key = generator.GetKeys(KeyTypeFlags.DacDbiKeys).SingleOrDefault((k) => Path.GetFileName(k.FullPathName) == fileName);
+ }
+ else if (platform == OSPlatform.Linux)
+ {
+ if (!RuntimeModule.BuildId.IsDefaultOrEmpty)
+ {
+ IEnumerable<SymbolStoreKey> keys = ELFFileKeyGenerator.GetKeys(
+ KeyTypeFlags.DacDbiKeys,
+ RuntimeModule.FileName,
+ RuntimeModule.BuildId.ToArray(),
+ symbolFile: false,
+ symbolFileName: null);
+
+ key = keys.SingleOrDefault((k) => Path.GetFileName(k.FullPathName) == fileName);
+ }
+ }
+ else if (platform == OSPlatform.Windows)
+ {
+ // Use the coreclr.dll's id (timestamp/filesize) to download the the dac module.
+ key = PEFileKeyGenerator.GetKey(fileName, (uint)RuntimeModule.IndexTimeStamp, (uint)RuntimeModule.IndexFileSize);
+ }
+
+ if (key != null)
+ {
+ // Now download the DAC module from the symbol server
+ filePath = SymbolService.DownloadFile(key);
+ }
+ else
+ {
+ Trace.TraceInformation($"DownloadFile: {fileName}: key not generated");
+ }
+ }
+ else
+ {
+ Trace.TraceInformation($"DownLoadFile: {fileName}: symbol store not enabled");
+ }
+ return filePath;
+ }
+
+ private IMemoryService MemoryService
+ {
+ get
+ {
+ if (_memoryService == null) {
+ _memoryService = _target.Services.GetService<IMemoryService>();
+ }
+ return _memoryService;
+ }
+ }
+
+ private ISymbolService SymbolService
+ {
+ get
+ {
+ if (_symbolService == null) {
+ _symbolService = _target.Services.GetService<ISymbolService>();
+ }
+ return _symbolService;
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Id == ((Runtime)obj).Id;
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ private static readonly string[] s_runtimeTypeNames = {
+ "Desktop .NET Framework",
+ ".NET Core",
+ ".NET Core (single-file)",
+ "Unknown"
+ };
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ string config = s_runtimeTypeNames[(int)RuntimeType];
+ sb.AppendLine($"#{Id} {config} runtime at {RuntimeModule.ImageBase:X16} size {RuntimeModule.IndexFileSize:X8}");
+ sb.AppendLine($" Runtime module path: {RuntimeModule.FileName}");
+ if (_dacFilePath != null) {
+ sb.AppendLine($" DAC: {_dacFilePath}");
+ }
+ if (_dbiFilePath != null) {
+ sb.AppendLine($" 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;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Architecture = System.Runtime.InteropServices.Architecture;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// ClrMD runtime service implementation
+ /// </summary>
+ public class RuntimeService : IRuntimeService, IDataReader
+ {
+ private readonly ITarget _target;
+ private DataTarget _dataTarget;
+ private string _runtimeModuleDirectory;
+ private List<Runtime> _runtimes;
+ private Runtime _currentRuntime;
+ private IModuleService _moduleService;
+ private IThreadService _threadService;
+ private IMemoryService _memoryService;
+
+ public RuntimeService(ITarget target)
+ {
+ _target = target;
+ target.OnFlushEvent += (object sender, EventArgs e) => {
+ if (_runtimes != null)
+ {
+ if (_runtimes.Count > 0)
+ {
+ foreach (Runtime runtime in _runtimes)
+ {
+ runtime.Flush();
+ }
+ }
+ else
+ {
+ // If there are no runtimes, try find them again when the target stops
+ _runtimes = null;
+ _dataTarget?.Dispose();
+ _dataTarget = null;
+ }
+ }
+ };
+ }
+
+ /// <summary>
+ /// Releases runtime services's resources.
+ /// </summary>
+ public void Close()
+ {
+ // BUGBUG - call this on target close
+ _dataTarget?.Dispose();
+ _dataTarget = null;
+ }
+
+ #region IRuntimeService
+
+ /// <summary>
+ /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.)
+ /// </summary>
+ public string RuntimeModuleDirectory
+ {
+ get { return _runtimeModuleDirectory; }
+ set
+ {
+ _runtimeModuleDirectory = value;
+ _runtimes = null;
+ _currentRuntime = null;
+ }
+ }
+
+ /// <summary>
+ /// Returns the list of runtimes in the target
+ /// </summary>
+ public IEnumerable<IRuntime> Runtimes => BuildRuntimes();
+
+ /// <summary>
+ /// Returns the current runtime
+ /// </summary>
+ public IRuntime CurrentRuntime
+ {
+ get
+ {
+ if (_currentRuntime == null) {
+ _currentRuntime = FindRuntime();
+ }
+ return _currentRuntime;
+ }
+ }
+
+ /// <summary>
+ /// Set the current runtime
+ /// </summary>
+ /// <param name="runtimeId">runtime id</param>
+ public void SetCurrentRuntime(int runtimeId)
+ {
+ if (_runtimes == null || runtimeId >= _runtimes.Count) {
+ throw new DiagnosticsException($"Invalid runtime id {runtimeId}");
+ }
+ _currentRuntime = _runtimes[runtimeId];
+ }
+
+ #endregion
+
+ #region IDataReader
+
+ string IDataReader.DisplayName => "";
+
+ bool IDataReader.IsThreadSafe => false;
+
+ OSPlatform IDataReader.TargetPlatform => _target.OperatingSystem;
+
+ Microsoft.Diagnostics.Runtime.Architecture IDataReader.Architecture
+ {
+ get
+ {
+ return _target.Architecture switch
+ {
+ Architecture.X64 => Microsoft.Diagnostics.Runtime.Architecture.Amd64,
+ Architecture.X86 => Microsoft.Diagnostics.Runtime.Architecture.X86,
+ Architecture.Arm => Microsoft.Diagnostics.Runtime.Architecture.Arm,
+ Architecture.Arm64 => Microsoft.Diagnostics.Runtime.Architecture.Arm64,
+ _ => throw new PlatformNotSupportedException($"{_target.Architecture}"),
+ };
+ }
+ }
+
+ int IDataReader.ProcessId => unchecked((int)_target.ProcessId.GetValueOrDefault());
+
+ IEnumerable<ModuleInfo> IDataReader.EnumerateModules() =>
+ ModuleService.EnumerateModules().Select((module) => CreateModuleInfo(module)).ToList();
+
+ private ModuleInfo CreateModuleInfo(IModule module) =>
+ new ModuleInfo(this, module.ImageBase, module.FileName, isVirtual:true, module.IndexFileSize, module.IndexTimeStamp, new ImmutableArray<byte>());
+
+ ImmutableArray<byte> IDataReader.GetBuildId(ulong baseAddress)
+ {
+ try
+ {
+ return ModuleService.GetModuleFromBaseAddress(baseAddress).BuildId;
+ }
+ catch (DiagnosticsException ex)
+ {
+ Trace.TraceError($"GetBuildId: {baseAddress:X16} exception {ex.Message}");
+ }
+ return ImmutableArray<byte>.Empty;
+ }
+
+ bool IDataReader.GetVersionInfo(ulong baseAddress, out VersionInfo version)
+ {
+ try
+ {
+ VersionInfo? v = ModuleService.GetModuleFromBaseAddress(baseAddress).Version;
+ if (v.HasValue)
+ {
+ version = v.Value;
+ return true;
+ }
+ }
+ catch (DiagnosticsException ex)
+ {
+ Trace.TraceError($"GetVersionInfo: {baseAddress:X16} exception {ex.Message}");
+ }
+ version = default;
+ return false;
+ }
+
+ bool IDataReader.GetThreadContext(uint threadId, uint contextFlags, Span<byte> context)
+ {
+ try
+ {
+ byte[] registerContext = ThreadService.GetThreadInfoFromId(threadId).GetThreadContext();
+ context = new Span<byte>(registerContext);
+ return true;
+ }
+ catch (DiagnosticsException ex)
+ {
+ Trace.TraceError($"GetThreadContext: {threadId} exception {ex.Message}");
+ }
+ return false;
+ }
+
+ void IDataReader.FlushCachedData() => _target.Flush();
+
+ #endregion
+
+ #region IMemoryReader
+
+ int IMemoryReader.PointerSize => MemoryService.PointerSize;
+
+ int IMemoryReader.Read(ulong address, Span<byte> buffer)
+ {
+ MemoryService.ReadMemory(address, buffer, out int bytesRead);
+ return bytesRead;
+ }
+
+ unsafe bool IMemoryReader.Read<T>(ulong address, out T value)
+ {
+ Span<byte> buffer = stackalloc byte[sizeof(T)];
+ if (((IMemoryReader)this).Read(address, buffer) == buffer.Length)
+ {
+ value = Unsafe.As<byte, T>(ref MemoryMarshal.GetReference(buffer));
+ return true;
+ }
+ value = default;
+ return false;
+ }
+
+ T IMemoryReader.Read<T>(ulong address)
+ {
+ ((IMemoryReader)this).Read(address, out T result);
+ return result;
+ }
+
+ bool IMemoryReader.ReadPointer(ulong address, out ulong value)
+ {
+ return MemoryService.ReadPointer(address, out value);
+ }
+
+ ulong IMemoryReader.ReadPointer(ulong address)
+ {
+ MemoryService.ReadPointer(address, out ulong value);
+ return value;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Find the runtime
+ /// </summary>
+ private Runtime FindRuntime()
+ {
+ IEnumerable<Runtime> runtimes = BuildRuntimes();
+ Runtime runtime = null;
+
+ // First check if there is a .NET Core runtime loaded
+ foreach (Runtime r in runtimes)
+ {
+ if (r.RuntimeType == RuntimeType.NetCore || r.RuntimeType == RuntimeType.SingleFile)
+ {
+ runtime = r;
+ break;
+ }
+ }
+ // If no .NET Core runtime, then check for desktop runtime
+ if (runtime == null)
+ {
+ foreach (Runtime r in runtimes)
+ {
+ if (r.RuntimeType == RuntimeType.Desktop)
+ {
+ runtime = r;
+ break;
+ }
+ }
+ }
+ return runtime;
+ }
+
+ private IEnumerable<Runtime> BuildRuntimes()
+ {
+ if (_runtimes == null)
+ {
+ _runtimes = new List<Runtime>();
+ if (_dataTarget == null)
+ {
+ _dataTarget = new DataTarget(new CustomDataTarget(this) {
+ BinaryLocator = new BinaryLocator(_target)
+ });
+ }
+ if (_dataTarget != null)
+ {
+ for (int i = 0; i < _dataTarget.ClrVersions.Length; i++)
+ {
+ _runtimes.Add(new Runtime(_target, this, _dataTarget.ClrVersions[i], i));
+ }
+ }
+ }
+ return _runtimes;
+ }
+
+ private IModuleService ModuleService
+ {
+ get
+ {
+ if (_moduleService == null)
+ {
+ _moduleService = _target.Services.GetService<IModuleService>();
+ }
+ return _moduleService;
+ }
+ }
+
+ private IMemoryService MemoryService
+ {
+ get
+ {
+ if (_memoryService == null)
+ {
+ _memoryService = _currentRuntime?.Services.GetService<IMemoryService>() ?? _target.Services.GetService<IMemoryService>();
+ }
+ return _memoryService;
+ }
+ }
+
+ private IThreadService ThreadService
+ {
+ get
+ {
+ if (_threadService == null)
+ {
+ _threadService = _target.Services.GetService<IThreadService>();
+ }
+ return _threadService;
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ if (_runtimeModuleDirectory != null) {
+ sb.AppendLine($"Runtime module path: {_runtimeModuleDirectory}");
+ }
+ if (_runtimes != null)
+ {
+ foreach (IRuntime runtime in _runtimes)
+ {
+ string current = _runtimes.Count > 1 ? runtime == _currentRuntime ? "*" : " " : "";
+ sb.Append(current);
+ sb.AppendLine(runtime.ToString());
+ }
+ }
+ 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 System;
+using System.Collections.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ public class ServiceProvider : IServiceProvider
+ {
+ readonly IServiceProvider _parent;
+ readonly Dictionary<Type, Func<object>> _factories;
+ readonly Dictionary<Type, object> _services;
+
+ /// <summary>
+ /// Create a service provider instance
+ /// </summary>
+ public ServiceProvider()
+ : this(null, null)
+ {
+ }
+
+ /// <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(parent, null)
+ {
+ }
+
+ /// <summary>
+ /// Create 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">a dictionary of the factories to create the services</param>
+ public ServiceProvider(IServiceProvider parent, Dictionary<Type, Func<object>> factories)
+ {
+ _parent = parent;
+ _factories = 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>
+ /// Remove the service instance for the type.
+ /// </summary>
+ /// <param name="type">service type</param>
+ public void RemoveService(Type type) => _services.Remove(type);
+
+ /// <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 && _parent != null)
+ {
+ 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 Microsoft.FileFormats;
+using Microsoft.SymbolStore;
+using Microsoft.SymbolStore.KeyGenerators;
+using SOS;
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection.PortableExecutable;
+using System.Text;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Symbol services to configure symbol servers, caches and directory search
+ /// </summary>
+ public class SymbolService : ISymbolService
+ {
+ private readonly IHost _host;
+
+ public SymbolService(IHost host)
+ {
+ _host = host;
+ }
+
+ /// <summary>
+ /// Invoked when anything changes in the symbol service (adding servers, caches, or directories, clearing store, etc.)
+ /// </summary>
+ public event ISymbolService.ChangeEventHandler OnChangeEvent;
+
+ /// <summary>
+ /// Returns true if symbol download has been enabled.
+ /// </summary>
+ public bool IsSymbolStoreEnabled => SymbolReader.IsSymbolStoreEnabled();
+
+ /// <summary>
+ /// The default symbol cache path:
+ ///
+ /// * dbgeng on Windows uses the dbgeng symbol cache path: %PROGRAMDATA%\dbg\sym
+ /// * dotnet-dump on Windows uses the VS symbol cache path: %TEMPDIR%\SymbolCache
+ /// * dotnet-dump/lldb on Linux/MacOS uses: $HOME/.dotnet/symbolcache
+ /// </summary>
+ public string DefaultSymbolCache
+ {
+ get { return SymbolReader.DefaultSymbolCache; }
+ set { SymbolReader.DefaultSymbolCache = value; }
+ }
+
+ /// <summary>
+ /// Parses the Windows debugger symbol path (srv*, cache*, etc.).
+ /// </summary>
+ /// <param name="symbolPath">Windows symbol path</param>
+ /// <returns>if false, error parsing symbol path</returns>
+ public bool ParseSymbolPath(string symbolPath)
+ {
+ OnChangeEvent?.Invoke(this, new EventArgs());
+ return SymbolReader.InitializeSymbolStore(
+ logging: false,
+ msdl: false,
+ symweb: false,
+ tempDirectory: null,
+ symbolServerPath: null,
+ authToken: null,
+ timeoutInMinutes: 0,
+ symbolCachePath: null,
+ symbolDirectoryPath: null,
+ symbolPath);
+ }
+
+ /// <summary>
+ /// Add symbol server to search path.
+ /// </summary>
+ /// <param name="msdl">if true, use the public Microsoft server</param>
+ /// <param name="symweb">if true, use symweb internal server and protocol (file.ptr)</param>
+ /// <param name="symbolServerPath">symbol server url (optional)</param>
+ /// <param name="authToken"></param>
+ /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional)</param>
+ /// <returns>if false, failure</returns>
+ public bool AddSymbolServer(
+ bool msdl,
+ bool symweb,
+ string symbolServerPath,
+ string authToken,
+ int timeoutInMinutes)
+ {
+ return SymbolReader.InitializeSymbolStore(
+ logging: false,
+ msdl,
+ symweb,
+ tempDirectory: null,
+ symbolServerPath,
+ authToken,
+ timeoutInMinutes,
+ symbolCachePath: null,
+ symbolDirectoryPath: null,
+ windowsSymbolPath: null);
+ }
+
+ /// <summary>
+ /// Add cache path to symbol search path
+ /// </summary>
+ /// <param name="symbolCachePath">symbol cache directory path (optional)</param>
+ public void AddCachePath(string symbolCachePath)
+ {
+ if (symbolCachePath == null) throw new ArgumentNullException(nameof(symbolCachePath));
+
+ SymbolReader.InitializeSymbolStore(
+ logging: false,
+ msdl: false,
+ symweb: false,
+ tempDirectory: null,
+ symbolServerPath: null,
+ authToken: null,
+ timeoutInMinutes: 0,
+ symbolCachePath,
+ symbolDirectoryPath: null,
+ windowsSymbolPath: null);
+ }
+
+ /// <summary>
+ /// Add directory path to symbol search path
+ /// </summary>
+ /// <param name="symbolDirectoryPath">symbol directory path to search (optional)</param>
+ public void AddDirectoryPath(string symbolDirectoryPath)
+ {
+ if (symbolDirectoryPath == null) throw new ArgumentNullException(nameof(symbolDirectoryPath));
+
+ SymbolReader.InitializeSymbolStore(
+ logging: false,
+ msdl: false,
+ symweb: false,
+ tempDirectory: null,
+ symbolServerPath: null,
+ authToken: null,
+ timeoutInMinutes: 0,
+ symbolCachePath: null,
+ symbolDirectoryPath,
+ windowsSymbolPath: null);
+ }
+
+ /// <summary>
+ /// This function disables any symbol downloading support.
+ /// </summary>
+ public void DisableSymbolStore()
+ {
+ SymbolReader.DisableSymbolStore();
+ }
+
+ /// <summary>
+ /// Download a file from the symbol stores/server.
+ /// </summary>
+ /// <param name="key">index of the file to download</param>
+ /// <returns>path to the downloaded file either in the cache or in the temp directory or null if error</returns>
+ public string DownloadFile(SymbolStoreKey key)
+ {
+ return SymbolReader.GetSymbolFile(key);
+ }
+
+ /// <summary>
+ /// Attempts to download/retrieve from cache the key.
+ /// </summary>
+ /// <param name="key">index of the file to retrieve</param>
+ /// <returns>stream or null</returns>
+ public SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key)
+ {
+ if (IsSymbolStoreEnabled)
+ {
+ return SymbolReader.GetSymbolStoreFile(key);
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Returns the metadata for the assembly
+ /// </summary>
+ /// <param name="imagePath">file name and path to module</param>
+ /// <param name="imageTimestamp">module timestamp</param>
+ /// <param name="imageSize">size of PE image</param>
+ /// <returns>metadata</returns>
+ public ImmutableArray<byte> GetMetadata(string imagePath, uint imageTimestamp, uint imageSize)
+ {
+ try
+ {
+ Stream peStream = null;
+ if (imagePath != null && File.Exists(imagePath))
+ {
+ peStream = TryOpenFile(imagePath);
+ }
+ else if (IsSymbolStoreEnabled)
+ {
+ SymbolStoreKey key = PEFileKeyGenerator.GetKey(imagePath, imageTimestamp, imageSize);
+ peStream = GetSymbolStoreFile(key)?.Stream;
+ }
+ if (peStream != null)
+ {
+ using var peReader = new PEReader(peStream, PEStreamOptions.Default);
+ if (peReader.HasMetadata)
+ {
+ PEMemoryBlock metadataInfo = peReader.GetMetadata();
+ return metadataInfo.GetContent();
+ }
+ }
+ }
+ catch (Exception ex) when
+ (ex is UnauthorizedAccessException ||
+ ex is BadImageFormatException ||
+ ex is InvalidVirtualAddressException ||
+ ex is IOException)
+ {
+ Trace.TraceError($"GetMetaData: {ex.Message}");
+ }
+ return ImmutableArray<byte>.Empty;
+ }
+
+ /// <summary>
+ /// Displays the symbol server and cache configuration
+ /// </summary>
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Attempt to open a file stream.
+ /// </summary>
+ /// <param name="path">file path</param>
+ /// <returns>stream or null if doesn't exist or error</returns>
+ private Stream TryOpenFile(string path)
+ {
+ if (File.Exists(path))
+ {
+ try
+ {
+ return File.OpenRead(path);
+ }
+ catch (Exception ex) when (ex is UnauthorizedAccessException || ex is NotSupportedException || ex is IOException)
+ {
+ Trace.TraceError($"TryOpenFile: {ex.Message}");
+ }
+ }
+ return null;
+ }
+ }
+}
--- /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.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Architecture = System.Runtime.InteropServices.Architecture;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// ITarget base implementation
+ /// </summary>
+ public abstract class Target : ITarget
+ {
+ private static int _targetIdFactory;
+ private readonly string _dumpPath;
+ private string _tempDirectory;
+
+ public readonly ServiceProvider ServiceProvider;
+
+ public Target(IHost host, string dumpPath)
+ {
+ Trace.TraceInformation($"Creating target #{Id}");
+ Host = host;
+ _dumpPath = dumpPath;
+
+ // Initialize the per-target services
+ ServiceProvider = new ServiceProvider(host.Services);
+
+ // Add the per-target services
+ ServiceProvider.AddService<ITarget>(this);
+ ServiceProvider.AddServiceFactory<IRuntimeService>(() => new RuntimeService(this));
+ }
+
+ #region ITarget
+
+ /// <summary>
+ /// Returns the host interface instance
+ /// </summary>
+ public IHost Host { get; }
+
+ /// <summary>
+ /// Invoked when this target is flushed (via the Flush() call).
+ /// </summary>
+ public event ITarget.FlushEventHandler OnFlushEvent;
+
+ /// <summary>
+ /// The target id
+ /// </summary>
+ public int Id { get; } = _targetIdFactory++;
+
+ /// <summary>
+ /// Returns the target OS (which may be different from the OS this is running on)
+ /// </summary>
+ public OSPlatform OperatingSystem { get; protected set; }
+
+ /// <summary>
+ /// The target architecture/processor
+ /// </summary>
+ public Architecture Architecture { get; protected set; }
+
+ /// <summary>
+ /// Returns true if dump, false if live session or snapshot
+ /// </summary>
+ public bool IsDump { get; protected set; }
+
+ /// <summary>
+ /// The target's process id or null no process
+ /// </summary>
+ public uint? ProcessId { get; protected set; }
+
+ /// <summary>
+ /// Returns the unique temporary directory for this instance of SOS
+ /// </summary>
+ public string GetTempDirectory()
+ {
+ if (_tempDirectory == null)
+ {
+ // Use the SOS process's id if can't get the target's
+ uint processId = ProcessId.GetValueOrDefault((uint)Process.GetCurrentProcess().Id);
+
+ // SOS depends on that the temp directory ends with "/".
+ _tempDirectory = Path.Combine(Path.GetTempPath(), "sos" + processId.ToString()) + Path.DirectorySeparatorChar;
+ Directory.CreateDirectory(_tempDirectory);
+ }
+ return _tempDirectory;
+ }
+
+ /// <summary>
+ /// The per target services.
+ /// </summary>
+ public IServiceProvider Services => ServiceProvider;
+
+ /// <summary>
+ /// Flushes any cached state in the target.
+ /// </summary>
+ public void Flush()
+ {
+ Trace.TraceInformation($"Flushing target #{Id}");
+ OnFlushEvent?.Invoke(this, EventArgs.Empty);
+ }
+
+ /// <summary>
+ /// Releases the target and the target's resources.
+ /// </summary>
+ public void Close()
+ {
+ Trace.TraceInformation($"Closing target #{Id}");
+ Flush();
+ CleanupTempDirectory();
+ }
+
+ #endregion
+
+ private void CleanupTempDirectory()
+ {
+ if (_tempDirectory != null)
+ {
+ try
+ {
+ foreach (string file in Directory.EnumerateFiles(_tempDirectory))
+ {
+ File.Delete(file);
+ }
+ Directory.Delete(_tempDirectory);
+ }
+ catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
+ {
+ }
+ _tempDirectory = null;
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Id == ((Target)obj).Id;
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ string process = ProcessId.HasValue ? string.Format("{0} (0x{0:X})", ProcessId.Value) : "<none>";
+ sb.AppendLine($"Target OS: {OperatingSystem} Architecture: {Architecture} ProcessId: {process}");
+ if (_tempDirectory != null) {
+ sb.AppendLine($"Temp path: {_tempDirectory}");
+ }
+ if (_dumpPath != null) {
+ sb.AppendLine($"Dump path: {_dumpPath}");
+ }
+ var runtimeService = ServiceProvider.GetService<IRuntimeService>();
+ if (runtimeService != null)
+ {
+ sb.AppendLine(runtimeService.ToString());
+ }
+ 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 Microsoft.Diagnostics.Runtime.DataReaders.Implementation;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Runtime.InteropServices;
+using Architecture = System.Runtime.InteropServices.Architecture;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// ITarget implementation for the ClrMD IDataReader
+ /// </summary>
+ public class TargetFromDataReader : Target
+ {
+ private readonly IDataReader _dataReader;
+
+ /// <summary>
+ /// Create a target instance from IDataReader
+ /// </summary>
+ /// <param name="dataReader">IDataReader</param>
+ /// <param name="targetOS">target operating system</param>
+ /// <param name="host">the host instance</param>
+ /// <param name="dumpPath">path of dump for this target</param>
+ public TargetFromDataReader(IDataReader dataReader, OSPlatform targetOS, IHost host, string dumpPath)
+ : base(host, dumpPath)
+ {
+ _dataReader = dataReader;
+
+ OperatingSystem = targetOS;
+ IsDump = true;
+ OnFlushEvent += (object sender, EventArgs e) => {
+ dataReader.FlushCachedData();
+ };
+
+ Architecture = dataReader.Architecture switch
+ {
+ Microsoft.Diagnostics.Runtime.Architecture.Amd64 => Architecture.X64,
+ Microsoft.Diagnostics.Runtime.Architecture.X86 => Architecture.X86,
+ Microsoft.Diagnostics.Runtime.Architecture.Arm => Architecture.Arm,
+ Microsoft.Diagnostics.Runtime.Architecture.Arm64 => Architecture.Arm64,
+ _ => throw new PlatformNotSupportedException($"{dataReader.Architecture}"),
+ };
+
+ if (dataReader.ProcessId != -1) {
+ ProcessId = (uint)dataReader.ProcessId;
+ }
+
+ // Add the thread, memory, and module services
+ ServiceProvider.AddServiceFactory<IThreadService>(() => new ThreadServiceFromDataReader(this, _dataReader));
+ ServiceProvider.AddServiceFactory<IModuleService>(() => new ModuleServiceFromDataReader(this, _dataReader));
+ ServiceProvider.AddServiceFactory<IMemoryService>(() => {
+ IMemoryService memoryService = new MemoryServiceFromDataReader(_dataReader);
+ if (Host.HostType == HostType.DotnetDump && OperatingSystem == OSPlatform.Windows) {
+ memoryService = new PEImageMappingMemoryService(this, memoryService);
+ }
+ return memoryService;
+ });
+ }
+ }
+}
--- /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.Implementation
+{
+ public class Thread : IThread
+ {
+ private readonly ThreadService _threadService;
+ private byte[] _threadContext;
+ private ulong? _teb;
+
+ public readonly ServiceProvider ServiceProvider;
+
+ public Thread(ThreadService threadService, int index, uint id)
+ {
+ _threadService = threadService;
+ ThreadIndex = index;
+ ThreadId = id;
+ ServiceProvider = new ServiceProvider();
+ }
+
+ #region IThread
+
+ public IServiceProvider Services => ServiceProvider;
+
+ public int ThreadIndex { get; }
+
+ public uint ThreadId { get; }
+
+ public bool GetRegisterValue(int index, out ulong value)
+ {
+ value = 0;
+
+ if (_threadService.GetRegisterInfo(index, out RegisterInfo info))
+ {
+ try
+ {
+ byte[] threadContext = GetThreadContext();
+ unsafe
+ {
+ fixed (byte* ptr = threadContext)
+ {
+ switch (info.RegisterSize)
+ {
+ case 1:
+ value = *((byte*)(ptr + info.RegisterOffset));
+ return true;
+ case 2:
+ value = *((ushort*)(ptr + info.RegisterOffset));
+ return true;
+ case 4:
+ value = *((uint*)(ptr + info.RegisterOffset));
+ return true;
+ case 8:
+ value = *((ulong*)(ptr + info.RegisterOffset));
+ return true;
+ }
+ }
+ }
+ }
+ catch (DiagnosticsException)
+ {
+ }
+ }
+ return false;
+ }
+
+ public byte[] GetThreadContext()
+ {
+ if (_threadContext == null)
+ {
+ _threadContext = _threadService.GetThreadContext(this);
+ }
+ return _threadContext;
+ }
+
+ public ulong GetThreadTeb()
+ {
+ if (!_teb.HasValue)
+ {
+ _teb = _threadService.GetThreadTeb(this);
+ }
+ return _teb.Value;
+ }
+
+ #endregion
+
+ public override string ToString()
+ {
+ return $"#{ThreadIndex} {ThreadId:X8}";
+ }
+ }
+}
\ No newline at end of file
--- /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.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Architecture = System.Runtime.InteropServices.Architecture;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Provides thread and register info and values for the clrmd IDataReader
+ /// </summary>
+ public abstract class ThreadService : IThreadService
+ {
+ 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;
+
+ target.OnFlushEvent += (object sender, EventArgs e) => {
+ _threads?.Clear();
+ _threads = null;
+ };
+
+ Type contextType;
+ 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;
+ _contextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments | AMD64Context.ContextFloatingPoint;
+ contextType = typeof(AMD64Context);
+ break;
+
+ case Architecture.X86:
+ _contextSize = X86Context.Size;
+ _contextFlags = X86Context.ContextControl | X86Context.ContextInteger | X86Context.ContextSegments | X86Context.ContextFloatingPoint;
+ contextType = typeof(X86Context);
+ break;
+
+ case Architecture.Arm64:
+ _contextSize = Arm64Context.Size;
+ _contextFlags = Arm64Context.ContextControl | Arm64Context.ContextInteger | Arm64Context.ContextFloatingPoint;
+ contextType = typeof(Arm64Context);
+ break;
+
+ case Architecture.Arm:
+ _contextSize = ArmContext.Size;
+ _contextFlags = ArmContext.ContextControl | ArmContext.ContextInteger | ArmContext.ContextFloatingPoint;
+ contextType = typeof(ArmContext);
+ break;
+
+ default:
+ throw new PlatformNotSupportedException($"Unsupported architecture: {target.Architecture}");
+ }
+
+ var registers = new List<RegisterInfo>();
+ int index = 0;
+
+ 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) {
+ continue;
+ }
+ RegisterType registerType = registerAttribute.RegisterType & RegisterType.TypeMask;
+ switch (registerType)
+ {
+ case RegisterType.Control:
+ case RegisterType.General:
+ case RegisterType.Segments:
+ break;
+ default:
+ continue;
+ }
+ if ((registerAttribute.RegisterType & RegisterType.ProgramCounter) != 0) {
+ InstructionPointerIndex = index;
+ }
+ if ((registerAttribute.RegisterType & RegisterType.StackPointer) != 0) {
+ StackPointerIndex = index;
+ }
+ if ((registerAttribute.RegisterType & RegisterType.FramePointer) != 0) {
+ FramePointerIndex = index;
+ }
+ FieldOffsetAttribute offsetAttribute = field.GetCustomAttributes<FieldOffsetAttribute>(inherit: false).Single();
+ var registerInfo = new RegisterInfo(index, offsetAttribute.Value, Marshal.SizeOf(field.FieldType), registerAttribute.Name ?? field.Name.ToLower());
+ registers.Add(registerInfo);
+ index++;
+ }
+
+ _lookupByName = registers.ToDictionary((info) => info.RegisterName);
+ _lookupByIndex = registers.ToDictionary((info) => info.RegisterIndex);
+ Registers = registers;
+ }
+
+ #region IThreadService
+
+ /// <summary>
+ /// Details on all the supported registers
+ /// </summary>
+ public IEnumerable<RegisterInfo> Registers { get; }
+
+ /// <summary>
+ /// The instruction pointer register index
+ /// </summary>
+ public int InstructionPointerIndex { get; }
+
+ /// <summary>
+ /// The frame pointer register index
+ /// </summary>
+ public int FramePointerIndex { get; }
+
+ /// <summary>
+ /// The stack pointer register index
+ /// </summary>
+ public int StackPointerIndex { get; }
+
+ /// <summary>
+ /// Return the register index for the register name
+ /// </summary>
+ /// <param name="name">register name</param>
+ /// <param name="index">returns register index or -1</param>
+ /// <returns>true if name found</returns>
+ public bool GetRegisterIndexByName(string name, out int index)
+ {
+ if (_lookupByName.TryGetValue(name, out RegisterInfo info))
+ {
+ index = info.RegisterIndex;
+ return true;
+ }
+ index = int.MaxValue;
+ return false;
+ }
+
+ /// <summary>
+ /// Returns the register info (name, offset, size, etc).
+ /// </summary>
+ /// <param name="index">register index</param>
+ /// <param name="info">RegisterInfo</param>
+ /// <returns>true if index found</returns>
+ public bool GetRegisterInfo(int index, out RegisterInfo info)
+ {
+ return _lookupByIndex.TryGetValue(index, out info);
+ }
+
+ /// <summary>
+ /// Current OS thread Id
+ /// </summary>
+ public virtual uint? CurrentThreadId { get; set; }
+
+ /// <summary>
+ /// Enumerate all the native threads
+ /// </summary>
+ /// <returns>ThreadInfos for all the threads</returns>
+ public IEnumerable<IThread> EnumerateThreads()
+ {
+ return GetThreads().OrderBy((pair) => pair.Value.ThreadIndex).Select((pair) => pair.Value);
+ }
+
+ /// <summary>
+ /// Get the thread info from the thread index
+ /// </summary>
+ /// <param name="threadIndex">index</param>
+ /// <returns>thread info</returns>
+ /// <exception cref="DiagnosticsException">invalid thread index</exception>
+ public IThread GetThreadInfoFromIndex(int threadIndex)
+ {
+ try
+ {
+ return GetThreads().First((pair) => pair.Value.ThreadIndex == threadIndex).Value;
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new DiagnosticsException($"Invalid thread index: {threadIndex}", ex);
+ }
+ }
+
+ /// <summary>
+ /// Get the thread info from the OS thread id
+ /// </summary>
+ /// <param name="threadId">os id</param>
+ /// <returns>thread info</returns>
+ /// <exception cref="DiagnosticsException">invalid thread id</exception>
+ public IThread GetThreadInfoFromId(uint threadId)
+ {
+ if (!GetThreads().TryGetValue(threadId, out IThread thread)) {
+ throw new DiagnosticsException($"Invalid thread id: {threadId}");
+ }
+ return thread;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Get the thread context
+ /// </summary>
+ /// <param name="thread">thread instance</param>
+ /// <returns>context array</returns>
+ internal byte[] GetThreadContext(Thread thread)
+ {
+ var threadContext = new byte[_contextSize];
+ if (!GetThreadContext(thread.ThreadId, _contextFlags, (uint)_contextSize, threadContext)) {
+ throw new DiagnosticsException();
+ }
+ return threadContext;
+ }
+
+ /// <summary>
+ /// Get the thread TEB
+ /// </summary>
+ /// <param name="thread">thread instance</param>
+ /// <returns>TEB</returns>
+ internal ulong GetThreadTeb(Thread thread)
+ {
+ return GetThreadTeb(thread.ThreadId);
+ }
+
+ /// <summary>
+ /// Get/create the thread dictionary.
+ /// </summary>
+ private Dictionary<uint, IThread> GetThreads()
+ {
+ if (_threads == null) {
+ _threads = GetThreadsInner().OrderBy((thread) => thread.ThreadId).ToDictionary((thread) => thread.ThreadId);
+ }
+ return _threads;
+ }
+
+ /// <summary>
+ /// Get/creates the threads.
+ /// </summary>
+ protected abstract IEnumerable<IThread> GetThreadsInner();
+
+ /// <summary>
+ /// Get the thread context
+ /// </summary>
+ /// <param name="threadId">OS thread id</param>
+ /// <param name="contextFlags">Windows context flags</param>
+ /// <param name="contextSize">Context size</param>
+ /// <param name="context">Context buffer</param>
+ /// <returns>true succeeded, false failed</returns>
+ /// <exception cref="DiagnosticsException">invalid thread id</exception>
+ protected abstract bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context);
+
+ /// <summary>
+ /// Returns the Windows TEB pointer for the thread
+ /// </summary>
+ /// <param name="threadId">OS thread id</param>
+ /// <returns>TEB pointer or 0</returns>
+ protected abstract ulong GetThreadTeb(uint threadId);
+ }
+}
--- /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 Microsoft.Diagnostics.Runtime.DataReaders.Implementation;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+ /// <summary>
+ /// Provides thread and register info and values for the clrmd IDataReader
+ /// </summary>
+ public class ThreadServiceFromDataReader : ThreadService
+ {
+ private readonly IDataReader _dataReader;
+ private readonly IThreadReader _threadReader;
+
+ public ThreadServiceFromDataReader(ITarget target, IDataReader dataReader)
+ : base(target)
+ {
+ _dataReader = dataReader;
+ _threadReader = (IThreadReader)dataReader;
+
+ if (dataReader is IThreadReader threadReader)
+ {
+ // Initialize the current thread
+ IEnumerable<uint> threads = threadReader.EnumerateOSThreadIds();
+ if (threads.Any()) {
+ CurrentThreadId = threads.First();
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException("IThreadReader not implemented");
+ }
+ }
+
+ protected override bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context)
+ {
+ try
+ {
+ return _dataReader.GetThreadContext(threadId, contextFlags, new Span<byte>(context, 0, unchecked((int)contextSize)));
+ }
+ catch (ClrDiagnosticsException ex)
+ {
+ Trace.TraceError(ex.ToString());
+ return false;
+ }
+ }
+
+ protected override IEnumerable<IThread> GetThreadsInner()
+ {
+ return _threadReader.EnumerateOSThreadIds().Select((uint id, int index) => new Thread(this, index, id)).Cast<IThread>();
+ }
+
+ protected override ulong GetThreadTeb(uint threadId)
+ {
+ return _threadReader.GetThreadTeb(threadId);
+ }
+ }
+}
+++ /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.Threading;
-
-namespace Microsoft.Diagnostics.DebugServices
-{
- /// <summary>
- /// Common context for commands
- /// </summary>
- public class AnalyzeContext
- {
- public AnalyzeContext()
- {
- }
-
- /// <summary>
- /// Current OS thread Id
- /// </summary>
- public uint? CurrentThreadId { get; set; }
-
- /// <summary>
- /// Cancellation token for current command
- /// </summary>
- public CancellationToken CancellationToken { get; set; }
-
- /// <summary>
- /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.)
- /// </summary>
- public string RuntimeModuleDirectory { get; set; }
- }
-}
\ No newline at end of file
+++ /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 Microsoft.SymbolStore;
-using Microsoft.SymbolStore.KeyGenerators;
-using SOS;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.DebugServices
-{
- #nullable enable
-
- /// <summary>
- /// A symbol locator that search binaries based on files loaded in the live Linux target.
- /// </summary>
- public class BinaryLocator : IBinaryLocator
- {
- public BinaryLocator()
- {
- }
-
- public string? FindBinary(string fileName, int buildTimeStamp, int imageSize, bool checkProperties)
- {
- Trace.TraceInformation($"FindBinary: {fileName} buildTimeStamp {buildTimeStamp:X8} imageSize {imageSize:X8}");
-
- if (SymbolReader.IsSymbolStoreEnabled())
- {
- SymbolStoreKey? key = PEFileKeyGenerator.GetKey(fileName, (uint)buildTimeStamp, (uint)imageSize);
- if (key != null)
- {
- // Now download the module from the symbol server, cache or from a directory
- return SymbolReader.GetSymbolFile(key);
- }
- else
- {
- Trace.TraceInformation($"DownloadFile: {fileName}: key not generated");
- }
- }
- else
- {
- Trace.TraceInformation($"DownLoadFile: {fileName}: symbol store not enabled");
- }
-
- return null;
- }
-
- public string? FindBinary(string fileName, ImmutableArray<byte> buildId, bool checkProperties)
- {
- Trace.TraceInformation($"FindBinary: {fileName} buildid {buildId}");
- return null;
- }
-
- public Task<string?> FindBinaryAsync(string fileName, ImmutableArray<byte> buildId, bool checkProperties)
- {
- Trace.TraceInformation($"FindBinaryAsync: {fileName} buildid {buildId}");
- return Task.FromResult<string?>(null);
- }
-
- public Task<string?> FindBinaryAsync(string fileName, int buildTimeStamp, int imageSize, bool checkProperties)
- {
- Trace.TraceInformation($"FindBinaryAsync: {fileName} buildTimeStamp {buildTimeStamp:X8} imageSize {imageSize:X8}");
- return Task.FromResult<string?>(null);
- }
- }
-}
--- /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>
+ /// OS Platforms to add command
+ /// </summary>
+ [Flags]
+ public enum CommandPlatform : byte
+ {
+ Windows = 0x01,
+ Linux = 0x02,
+ OSX = 0x04,
+
+ /// <summary>
+ /// Command is supported when there is no target
+ /// </summary>
+ Global = 0x08,
+
+ /// <summary>
+ /// Default. All operating system, but target is required
+ /// </summary>
+ Default = Windows | Linux | OSX
+ }
+
+ /// <summary>
+ /// Marks the class as a Command.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ public class CommandAttribute : Attribute
+ {
+ /// <summary>
+ /// Name of the command
+ /// </summary>
+ public string Name;
+
+ /// <summary>
+ /// Displayed in the help for the command and any aliases
+ /// </summary>
+ public string Help;
+
+ /// <summary>
+ /// The command's aliases
+ /// </summary>
+ public string[] Aliases = Array.Empty<string>();
+
+ /// <summary>
+ /// Optional OS platform for the command
+ /// </summary>
+ public CommandPlatform Platform = CommandPlatform.Default;
+
+ /// <summary>
+ /// A string of options that are parsed before the command line options
+ /// </summary>
+ public string DefaultOptions;
+ }
+
+ /// <summary>
+ /// Marks the property as a Option.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Property)]
+ public class OptionAttribute : Attribute
+ {
+ /// <summary>
+ /// Name of the option i.e "--pid"
+ /// </summary>
+ public string Name;
+
+ /// <summary>
+ /// Displayed in the help for the option and any aliases
+ /// </summary>
+ public string Help;
+
+ /// <summary>
+ /// The option's aliases i.e "-p"
+ /// </summary>
+ public string[] Aliases = Array.Empty<string>();
+ }
+
+ /// <summary>
+ /// Marks the property the command Argument.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Property)]
+ public class ArgumentAttribute : Attribute
+ {
+ /// <summary>
+ /// Name of the argument
+ /// </summary>
+ public string Name;
+
+ /// <summary>
+ /// Displayed in the help for the argument
+ /// </summary>
+ public string Help;
+ }
+
+ /// <summary>
+ /// Marks the function to invoke to execute the command.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public class CommandInvokeAttribute : Attribute
+ {
+ }
+
+ /// <summary>
+ /// Marks the function to invoke to display alternate help for command.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public class HelpInvokeAttribute : Attribute
+ {
+ }
+}
--- /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>
+ /// The common command context
+ /// </summary>
+ public abstract class CommandBase
+ {
+ /// <summary>
+ /// Console service
+ /// </summary>
+ public IConsoleService Console { get; set; }
+
+ /// <summary>
+ /// Execute the command
+ /// </summary>
+ [CommandInvoke]
+ public abstract void Invoke();
+
+ /// <summary>
+ /// Display text
+ /// </summary>
+ /// <param name="message">text message</param>
+ protected void Write(string message)
+ {
+ Console.Write(message);
+ }
+
+ /// <summary>
+ /// Display line
+ /// </summary>
+ /// <param name="message">line message</param>
+ protected void WriteLine(string message)
+ {
+ Console.Write(message + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Display formatted text
+ /// </summary>
+ /// <param name="format">format string</param>
+ /// <param name="args">arguments</param>
+ protected void WriteLine(string format, params object[] args)
+ {
+ Console.Write(string.Format(format, args) + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Display formatted warning text
+ /// </summary>
+ /// <param name="format">format string</param>
+ /// <param name="args">arguments</param>
+ protected void WriteLineWarning(string format, params object[] args)
+ {
+ Console.WriteWarning(string.Format(format, args) + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Display formatted error text
+ /// </summary>
+ /// <param name="format">format string</param>
+ /// <param name="args">arguments</param>
+ protected void WriteLineError(string format, params object[] args)
+ {
+ Console.WriteError(string.Format(format, args) + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Convert hexadecimal string address into ulong
+ /// </summary>
+ /// <param name="addressInHexa">0x12345678 or 000012345670 format are supported</param>
+ /// <param name="address">parsed hexadecimal address</param>
+ /// <returns></returns>
+ protected bool TryParseAddress(string addressInHexa, out ulong address)
+ {
+ // skip 0x or leading 0000 if needed
+ if (addressInHexa.StartsWith("0x"))
+ addressInHexa = addressInHexa.Substring(2);
+ addressInHexa = addressInHexa.TrimStart('0');
+
+ return ulong.TryParse(addressInHexa, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out address);
+ }
+ }
+}
--- /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.Linq;
+using System.Reflection;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ public static class CommandServiceExtensions
+ {
+ /// <summary>
+ /// Add commands from assemblies. Searches for the command and alias attributes in all the assemblies' types.
+ /// </summary>
+ /// <param name="commandService">command service instance</param>
+ /// <param name="assemblies">list of assemblies to search</param>
+ public static void AddCommands(this ICommandService commandService, IEnumerable<Assembly> assemblies)
+ {
+ commandService.AddCommands(assemblies.SelectMany((assembly) => assembly.GetExportedTypes()));
+ }
+
+ /// <summary>
+ /// Add commands from an assembly. Searches for the command and alias attributes in all the assembly's types.
+ /// </summary>
+ /// <param name="commandService">command service instance</param>
+ /// <param name="assembly">assembly to search for commands</param>
+ public static void AddCommands(this ICommandService commandService, Assembly assembly)
+ {
+ commandService.AddCommands(assembly.GetExportedTypes());
+ }
+
+ /// <summary>
+ /// Searches for the command and alias attributes in all types.
+ /// </summary>
+ /// <param name="commandService">command service instance</param>
+ /// <param name="types">list of types to search</param>
+ public static void AddCommands(this ICommandService commandService, IEnumerable<Type> 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);
+ }
+ }
+}
--- /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 ConsoleServiceExtensions
+ {
+ /// <summary>
+ /// Display text
+ /// </summary>
+ /// <param name="console">console service instance</param>
+ /// <param name="message">text message</param>
+ public static void WriteLine(this IConsoleService console, string message)
+ {
+ console.Write(message + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Display formatted text
+ /// </summary>
+ /// <param name="console">console service instance</param>
+ /// <param name="format">format string</param>
+ /// <param name="args">arguments</param>
+ public static void WriteLine(this IConsoleService console, string format, params object[] args)
+ {
+ console.Write(string.Format(format, args) + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Display formatted warning text
+ /// </summary>
+ /// <param name="console">console service instance</param>
+ /// <param name="format">format string</param>
+ /// <param name="args">arguments</param>
+ public static void WriteLineWarning(this IConsoleService console, string format, params object[] args)
+ {
+ console.WriteWarning(string.Format(format, args) + Environment.NewLine);
+ }
+
+ /// <summary>
+ /// Display formatted error text
+ /// </summary>
+ /// <param name="console">console service instance</param>
+ /// <param name="format">format string</param>
+ /// <param name="args">arguments</param>
+ public static void WriteLineError(this IConsoleService console, string format, params object[] args)
+ {
+ console.WriteError(string.Format(format, args) + Environment.NewLine);
+ }
+ }
+}
--- /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>
+ /// Command processor service
+ /// </summary>
+ public interface ICommandService
+ {
+ /// <summary>
+ /// Enumerates all the command's name and help
+ /// </summary>
+ IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands { get; }
+
+ /// <summary>
+ /// 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);
+ }
+}
// 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.Threading;
+
namespace Microsoft.Diagnostics.DebugServices
{
/// <summary>
void Write(string value);
/// <summary>
- /// Write text to console's standard error
+ /// Write warning text to console
+ /// </summary>
+ /// <param name="value"></param>
+ void WriteWarning(string value);
+
+ /// <summary>
+ /// Write error text to console
/// </summary>
/// <param name="value"></param>
void WriteError(string value);
/// <summary>
- /// Exit the interactive console
+ /// Cancellation token for current command
/// </summary>
- void Exit();
+ CancellationToken CancellationToken { get; set; }
}
-}
\ No newline at end of file
+}
--- /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>
+ /// The type of the debugger or host. Must match IHost::HostType.
+ /// </summary>
+ public enum HostType
+ {
+ DotnetDump,
+ Lldb,
+ DbgEng,
+ Vs
+ };
+
+ /// <summary>
+ /// Host interface
+ /// </summary>
+ public interface IHost
+ {
+ /// <summary>
+ /// Host shutdown event handler
+ /// </summary>
+ public delegate void ShutdownEventHandler(object sender, EventArgs e);
+
+ /// <summary>
+ /// Invoked on hosting debugger or dotnet-dump shutdown
+ /// </summary>
+ event ShutdownEventHandler OnShutdownEvent;
+
+ /// <summary>
+ /// Returns the hosting debugger type
+ /// </summary>
+ HostType HostType { get; }
+
+ /// <summary>
+ /// Global service provider
+ /// </summary>
+ IServiceProvider Services { get; }
+
+ /// <summary>
+ /// Current target instances or null
+ /// </summary>
+ ITarget CurrentTarget { get; }
+
+ /// <summary>
+ /// Sets the current target.
+ /// </summary>
+ /// <param name="targetid">target id</param>
+ void SetCurrentTarget(int targetid);
+ }
+}
--- /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>
+ /// Memory service
+ /// </summary>
+ public interface IMemoryService
+ {
+ /// <summary>
+ /// Returns the pointer size of the target
+ /// </summary>
+ int PointerSize { get; }
+
+ /// <summary>
+ /// Read memory out of the target process.
+ /// </summary>
+ /// <param name="address">The address of memory to read</param>
+ /// <param name="buffer">The buffer to read memory into</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
+ bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead);
+
+ /// <summary>
+ /// Write memory into target process for supported targets.
+ /// </summary>
+ /// <param name="address">The address of memory to write</param>
+ /// <param name="buffer">The buffer to write</param>
+ /// <param name="bytesWritten">The number of bytes successfully written</param>
+ /// <returns>true if any bytes where written, false if write failed</returns>
+ bool WriteMemory(ulong address, Span<byte> buffer, out int bytesWritten);
+ }
+}
--- /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.Immutable;
+using System.Reflection.PortableExecutable;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ /// <summary>
+ /// Details about a module
+ /// </summary>
+ public interface IModule
+ {
+ /// <summary>
+ /// The per module services like an optional clrmd's PEImage or PEReader instances if a PE module.
+ /// </summary>
+ IServiceProvider Services { get; }
+
+ /// <summary>
+ /// Debugger specific module index
+ /// </summary>
+ int ModuleIndex { get; }
+
+ /// <summary>
+ /// Gets the file name of the module on disk.
+ /// </summary>
+ string FileName { get; }
+
+ /// <summary>
+ /// Gets the base address of the object.
+ /// </summary>
+ ulong ImageBase { get; }
+
+ /// <summary>
+ /// Returns the image size of module in memory
+ /// </summary>
+ ulong ImageSize { get; }
+
+ /// <summary>
+ /// Gets the specific file size of the image used to index it on the symbol server.
+ /// </summary>
+ int IndexFileSize { get; }
+
+ /// <summary>
+ /// Gets the timestamp of the image used to index it on the symbol server.
+ /// </summary>
+ int IndexTimeStamp { get; }
+
+ /// <summary>
+ /// Build id on Linux and MacOS, otherwise empty value.
+ /// </summary>
+ ImmutableArray<byte> BuildId { get; }
+
+ /// <summary>
+ /// Returns true if Windows PE format image (native or IL)
+ /// </summary>
+ bool IsPEImage { get; }
+
+ /// <summary>
+ /// Returns true if managed or IL assembly
+ /// </summary>
+ bool IsManaged { get; }
+
+ /// <summary>
+ /// Returns true if the PE module is layout is file. False, layout is loaded image. Null, not a PE image.
+ /// </summary>
+ bool? IsFileLayout { get; }
+
+ /// <summary>
+ /// PDB information for Windows PE modules (managed or native
+ /// </summary>
+ PdbInfo PdbInfo { get; }
+
+ /// <summary>
+ /// Version information for Window PE modules (managed or native).
+ /// </summary>
+ VersionInfo? Version { get; }
+
+ /// <summary>
+ /// This is the file version string containing the build version and commit id.
+ /// </summary>
+ string VersionString { get; }
+ }
+}
--- /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>
+ /// Provides module info
+ /// </summary>
+ public interface IModuleService
+ {
+ /// <summary>
+ /// Enumerate all the modules in the target
+ /// </summary>
+ IEnumerable<IModule> EnumerateModules();
+
+ /// <summary>
+ /// Get the module from the module index
+ /// </summary>
+ /// <param name="moduleIndex">index</param>
+ /// <returns>module</returns>
+ /// <exception cref="DiagnosticsException">invalid module index</exception>
+ IModule GetModuleFromIndex(int moduleIndex);
+
+ /// <summary>
+ /// Get the module from the module base address
+ /// </summary>
+ /// <param name="baseAddress">module address</param>
+ /// <returns>module</returns>
+ /// <exception cref="DiagnosticsException">base address not found</exception>
+ IModule GetModuleFromBaseAddress(ulong baseAddress);
+
+ /// <summary>
+ /// Finds the module that contains the address.
+ /// </summary>
+ /// <param name="address">search address</param>
+ /// <returns>module or null</returns>
+ IModule GetModuleFromAddress(ulong address);
+
+ /// <summary>
+ /// Finds the module(s) with the specified module name. It is the platform dependent
+ /// name that includes the "lib" prefix on xplat and the extension (dll, so or dylib).
+ /// </summary>
+ /// <param name="moduleName">module name to find</param>
+ /// <returns>matching modules</returns>
+ IEnumerable<IModule> GetModuleFromModuleName(string moduleName);
+ }
+}
--- /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;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ /// <summary>
+ /// The runtime type.
+ /// </summary>
+ public enum RuntimeType
+ {
+ Desktop = 0,
+ NetCore = 1,
+ SingleFile = 2,
+ Unknown = 3
+ }
+
+ /// <summary>
+ /// Provides runtime info and instance
+ /// </summary>
+ public interface IRuntime
+ {
+ /// <summary>
+ /// The per target services like clrmd's ClrInfo and ClrRuntime.
+ /// </summary>
+ IServiceProvider Services { get; }
+
+ /// <summary>
+ /// Runtime id
+ /// </summary>
+ int Id { get; }
+
+ /// <summary>
+ /// Returns the runtime OS and type
+ /// </summary>
+ RuntimeType RuntimeType { get; }
+
+ /// <summary>
+ /// Returns the runtime module
+ /// </summary>
+ IModule RuntimeModule { get; }
+
+ /// <summary>
+ /// Returns the DAC file path
+ /// </summary>
+ string GetDacFilePath();
+
+ /// <summary>
+ /// Returns the DBI file path
+ /// </summary>
+ string GetDbiFilePath();
+ }
+}
--- /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 .NET runtime information
+ /// </summary>
+ public interface IRuntimeService
+ {
+ /// <summary>
+ /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.)
+ /// </summary>
+ string RuntimeModuleDirectory { get; set; }
+
+ /// <summary>
+ /// Returns the list of runtimes in the target
+ /// </summary>
+ public IEnumerable<IRuntime> Runtimes { get; }
+
+ /// <summary>
+ /// Returns the current runtime or null if no runtime was found
+ /// </summary>
+ public IRuntime CurrentRuntime { get; }
+
+ /// <summary>
+ /// Set the current runtime
+ /// </summary>
+ /// <param name="runtimeId">runtime id</param>
+ void SetCurrentRuntime(int runtimeId);
+ }
+}
--- /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.SymbolStore;
+using System;
+using System.Collections.Immutable;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ public interface ISymbolService
+ {
+ /// <summary>
+ /// Symbol store change event. The sender is the symbol store instance.
+ /// </summary>
+ public delegate void ChangeEventHandler(object sender, EventArgs e);
+
+ /// <summary>
+ /// Invoked when anything changes in the symbol service (adding servers, caches, or directories, clearing store, etc.)
+ /// </summary>
+ event ChangeEventHandler OnChangeEvent;
+
+ /// <summary>
+ /// Returns true if symbol download has been enabled.
+ /// </summary>
+ public bool IsSymbolStoreEnabled { get; }
+
+ /// <summary>
+ /// The default symbol cache path:
+ ///
+ /// * dbgeng on Windows uses the dbgeng symbol cache path: %PROGRAMDATA%\dbg\sym
+ /// * dotnet-dump on Windows uses the VS symbol cache path: %TEMPDIR%\SymbolCache
+ /// * dotnet-dump/lldb on Linux/MacOS uses: $HOME/.dotnet/symbolcache
+ /// </summary>
+ public string DefaultSymbolCache { get; set; }
+
+ /// <summary>
+ /// Parses the Windows debugger symbol path (srv*, cache*, etc.).
+ /// </summary>
+ /// <param name="symbolPath">Windows symbol path</param>
+ /// <returns>if false, error parsing symbol path</returns>
+ public bool ParseSymbolPath(string symbolPath);
+
+ /// <summary>
+ /// Add symbol server to search path.
+ /// </summary>
+ /// <param name="msdl">if true, use the public Microsoft server</param>
+ /// <param name="symweb">if true, use symweb internal server and protocol (file.ptr)</param>
+ /// <param name="symbolServerPath">symbol server url (optional)</param>
+ /// <param name="authToken"></param>
+ /// <param name="timeoutInMinutes">symbol server timeout in minutes (optional)</param>
+ /// <returns>if false, failure</returns>
+ public bool AddSymbolServer(bool msdl, bool symweb, string symbolServerPath, string authToken, int timeoutInMinutes);
+
+ /// <summary>
+ /// Add cache path to symbol search path
+ /// </summary>
+ /// <param name="symbolCachePath">symbol cache directory path (optional)</param>
+ public void AddCachePath(string symbolCachePath);
+
+ /// <summary>
+ /// Add directory path to symbol search path
+ /// </summary>
+ /// <param name="symbolDirectoryPath">symbol directory path to search (optional)</param>
+ public void AddDirectoryPath(string symbolDirectoryPath);
+
+ /// <summary>
+ /// This function disables any symbol downloading support.
+ /// </summary>
+ public void DisableSymbolStore();
+
+ /// <summary>
+ /// Download a file from the symbol stores/server.
+ /// </summary>
+ /// <param name="key">index of the file to download</param>
+ /// <returns>path to the downloaded file either in the cache or in the temp directory or null if error</returns>
+ public string DownloadFile(SymbolStoreKey key);
+
+ /// <summary>
+ /// Attempts to download/retrieve from cache the key.
+ /// </summary>
+ /// <param name="key">index of the file to retrieve</param>
+ /// <returns>stream or null</returns>
+ public SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key);
+
+ /// <summary>
+ /// Returns the metadata for the assembly
+ /// </summary>
+ /// <param name="imagePath">file name and path to module</param>
+ /// <param name="imageTimestamp">module timestamp</param>
+ /// <param name="imageSize">size of PE image</param>
+ /// <returns>metadata</returns>
+ public ImmutableArray<byte> GetMetadata(string imagePath, uint imageTimestamp, uint imageSize);
+ }
+}
--- /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.Runtime.InteropServices;
+using Architecture = System.Runtime.InteropServices.Architecture;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ /// <summary>
+ /// This interface represents the dump, snapshot or live session memory, threads and modules, etc.
+ /// </summary>
+ public interface ITarget
+ {
+ /// <summary>
+ /// Flush event handler delegate. The sender is the target instance.
+ /// </summary>
+ public delegate void FlushEventHandler(object sender, EventArgs e);
+
+ /// <summary>
+ /// Invoked when this target is flushed (via the Flush() call).
+ /// </summary>
+ event FlushEventHandler OnFlushEvent;
+
+ /// <summary>
+ /// Returns the host interface instance
+ /// </summary>
+ IHost Host { get; }
+
+ /// <summary>
+ /// The target id
+ /// </summary>
+ int Id { get; }
+
+ /// <summary>
+ /// Returns the target OS (which may be different from the OS this is running on)
+ /// </summary>
+ OSPlatform OperatingSystem { get; }
+
+ /// <summary>
+ /// The target architecture/processor
+ /// </summary>
+ /// <exception cref="PlatformNotSupportedException">unsupported architecture</exception>
+ Architecture Architecture { get; }
+
+ /// <summary>
+ /// Returns true if dump, false if live session or snapshot
+ /// </summary>
+ bool IsDump { get; }
+
+ /// <summary>
+ /// The target's process id or null if no process
+ /// </summary>
+ uint? ProcessId { get; }
+
+ /// <summary>
+ /// Returns the unique temporary directory for this instance of SOS
+ /// </summary>
+ string GetTempDirectory();
+
+ /// <summary>
+ /// The per target services.
+ /// </summary>
+ IServiceProvider Services { get; }
+
+ /// <summary>
+ /// Flushes any cached state in the target.
+ /// </summary>
+ void Flush();
+
+ /// <summary>
+ /// Releases the target and the target's resources.
+ /// </summary>
+ void Close();
+ }
+}
--- /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>
+ /// Details about a thread
+ /// </summary>
+ public interface IThread
+ {
+ /// <summary>
+ /// The per thread services.
+ /// </summary>
+ IServiceProvider Services { get; }
+
+ /// <summary>
+ /// Debugger specific thread index.
+ /// </summary>
+ int ThreadIndex { get; }
+
+ /// <summary>
+ /// OS thread id.
+ /// </summary>
+ uint ThreadId { get; }
+
+ /// <summary>
+ /// Returns the register value for the thread and register index. This function
+ /// can only return register values that are 64 bits or less and currently the
+ /// clrmd data targets don't return any floating point or larger registers.
+ /// </summary>
+ /// <param name="registerIndex">register index</param>
+ /// <param name="value">value returned</param>
+ /// <returns>true if value found</returns>
+ bool GetRegisterValue(int registerIndex, out ulong value);
+
+ /// <summary>
+ /// Returns the raw context buffer bytes for the specified thread.
+ /// </summary>
+ /// <returns>register context</returns>
+ /// <exception cref="DiagnosticsException">invalid thread</exception>
+ byte[] GetThreadContext();
+
+ /// <summary>
+ /// Returns the address of the Windows TEB or 0.
+ /// </summary>
+ /// <returns>TEB address</returns>
+ public ulong GetThreadTeb();
+ }
+}
bool GetRegisterInfo(int registerIndex, out RegisterInfo info);
/// <summary>
- /// Returns the register value for the thread and register index. This function
- /// can only return register values that are 64 bits or less and currently the
- /// clrmd data targets don't return any floating point or larger registers.
+ /// Current OS thread Id
/// </summary>
- /// <param name="threadId">thread id</param>
- /// <param name="registerIndex">register index</param>
- /// <param name="value">value returned</param>
- /// <returns>true if value found</returns>
- bool GetRegisterValue(uint threadId, int registerIndex, out ulong value);
-
- /// <summary>
- /// Returns the raw context buffer bytes for the specified thread.
- /// </summary>
- /// <param name="threadId">thread id</param>
- /// <returns>register context</returns>
- /// <exception cref="DiagnosticsException">invalid thread id</exception>
- byte[] GetThreadContext(uint threadId);
+ uint? CurrentThreadId { get; set; }
/// <summary>
/// Enumerate all the native threads
/// </summary>
- /// <returns>ThreadInfos for all the threads</returns>
- IEnumerable<ThreadInfo> EnumerateThreads();
+ /// <returns>Get info for all the threads</returns>
+ IEnumerable<IThread> EnumerateThreads();
/// <summary>
/// Get the thread info from the thread index
/// <param name="threadIndex">index</param>
/// <returns>thread info</returns>
/// <exception cref="DiagnosticsException">invalid thread index</exception>
- ThreadInfo GetThreadInfoFromIndex(int threadIndex);
+ IThread GetThreadInfoFromIndex(int threadIndex);
/// <summary>
/// Get the thread info from the OS thread id
/// <param name="threadId">os id</param>
/// <returns>thread info</returns>
/// <exception cref="DiagnosticsException">invalid thread id</exception>
- ThreadInfo GetThreadInfoFromId(uint threadId);
+ IThread GetThreadInfoFromId(uint threadId);
}
}
+++ /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
-{
- public sealed class MemoryCache
- {
- /// <summary>
- /// This class represents a chunk of cached memory, more or less a page.
- /// </summary>
- class Cluster
- {
- internal const int Size = 4096;
-
- /// <summary>
- /// Empty cluster
- /// </summary>
- internal static Cluster Empty = new Cluster(Array.Empty<byte>());
-
- /// <summary>
- /// The cached data.
- /// </summary>
- private readonly byte[] _data;
-
- /// <summary>
- /// Creates a cluster for some data.
- /// If the buffer is shorter than a page, it build a validity bitmap for it.
- /// </summary>
- /// <param name="data">the data to cache</param>
- ///
- internal Cluster(byte[] data)
- {
- _data = data;
- }
-
- /// <summary>
- /// Computes the base address of the cluster holding an address.
- /// </summary>
- /// <param name="address">input address</param>
- /// <returns>start address of the cluster</returns>
- internal static ulong GetBase(ulong address)
- {
- return address & ~(ulong)(Cluster.Size - 1);
- }
-
- /// <summary>
- /// Computes the offset of an address inside of the cluster.
- /// </summary>
- /// <param name="address">input address</param>
- /// <returns>offset of address</returns>
- internal static int GetOffset(ulong address)
- {
- return unchecked((int)((uint)address & (Cluster.Size - 1)));
- }
-
- /// <summary>
- /// Reads at up <paramref name="size"/> bytes from location <paramref name="address"/>.
- /// </summary>
- /// <param name="address">desired address</param>
- /// <param name="buffer">buffer to read</param>
- /// <param name="size">number of bytes to read</param>
- /// <returns>bytes read</returns>
- internal int ReadBlock(ulong address, Span<byte> buffer, int size)
- {
- int offset = GetOffset(address);
- if (offset < _data.Length)
- {
- size = Math.Min(_data.Length - offset, size);
- new Span<byte>(_data, offset, size).CopyTo(buffer);
- }
- else
- {
- size = 0;
- }
- return size;
- }
- }
-
- /// <summary>
- /// After memory cache reaches the limit size, it gets flushed upon next access.
- /// </summary>
- private const int CacheSizeLimit = 64 * 1024 * 1024; // 64 MB
-
- /// <summary>
- /// The delegate to the actual read memory
- /// </summary>
- public delegate byte[] ReadMemoryDelegate(ulong address, int size);
-
- private readonly Dictionary<ulong, Cluster> _map;
- private readonly ReadMemoryDelegate _readMemory;
-
- public MemoryCache(ReadMemoryDelegate readMemory)
- {
- _map = new Dictionary<ulong, Cluster>();
- _readMemory = readMemory;
- }
-
- /// <summary>
- /// Current size of this memory cache
- /// </summary>
- public long CacheSize { get; private set; }
-
- /// <summary>
- /// Flush this memory cache
- /// </summary>
- public void FlushCache()
- {
- Trace.TraceInformation("Flushed memory cache");
- _map.Clear();
- CacheSize = 0;
- }
-
- /// <summary>
- /// Reads up to <paramref name="buffer.Length"/> bytes of memory at <paramref name="address"/>.
- /// It walks the set of clusters to collect as much data as possible.
- /// </summary>
- /// <param name="address">address to read</param>
- /// <param name="buffer">span of buffer to read memory</param>
- /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
- /// <returns>true if read memory succeeded or partially succeeded</returns>
- public bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead)
- {
- int bytesRequested = buffer.Length;
- int offset = 0;
-
- while (bytesRequested > 0)
- {
- Cluster cluster = GetCluster(address);
- int read = cluster.ReadBlock(address, buffer.Slice(offset), bytesRequested);
- if (read <= 0) {
- break;
- }
- address += (uint)read;
- offset += read;
- bytesRequested -= read;
- }
-
- bytesRead = offset;
- return offset > 0;
- }
-
- /// <summary>
- /// Ensures that an address is cached.
- /// </summary>
- /// <param name="address">target address</param>
- /// <returns>It will resolve to an existing cluster or a newly-created one</returns>
- private Cluster GetCluster(ulong address)
- {
- ulong baseAddress = Cluster.GetBase(address);
-
- if (!_map.TryGetValue(baseAddress, out Cluster cluster))
- {
- if (CacheSize >= CacheSizeLimit)
- {
- FlushCache();
- }
-
- // There are 3 things that can happen here:
- // 1) Normal full size cluster read (== Cluster.Size). The full block memory is cached.
- // 2) Partial cluster read (< Cluster.Size). The partial memory block is cached and the memory after it is invalid.
- // 3) Data == null. Read failure. Failure is not cached.
- byte[] data = _readMemory(baseAddress, Cluster.Size);
- if (data == null)
- {
- cluster = Cluster.Empty;
- }
- else
- {
- cluster = new Cluster(data);
- CacheSize += data.Length;
- _map[baseAddress] = cluster;
- }
- }
-
- return cluster;
- }
- }
-}
+++ /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 Microsoft.SymbolStore;
-using Microsoft.SymbolStore.KeyGenerators;
-using SOS;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Reflection.Metadata;
-using System.Reflection.PortableExecutable;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Diagnostics.DebugServices
-{
- /// <summary>
- /// Memory service CLRMD implementation
- /// </summary>
- public class MemoryService
- {
- private readonly IDataReader _dataReader;
- private readonly MemoryCache _memoryCache;
- private readonly Dictionary<string, PEReader> _pathToPeReader = new Dictionary<string, PEReader>();
- private readonly ulong _ignoreAddressBitsMask;
-
- /// <summary>
- /// Memory service constructor
- /// </summary>
- /// <param name="dataReader">CLRMD data reader</param>
- public MemoryService(IDataReader dataReader)
- {
- _dataReader = dataReader;
- _memoryCache = new MemoryCache(ReadMemoryFromModule);
- _ignoreAddressBitsMask = dataReader.PointerSize == 4 ? uint.MaxValue : ulong.MaxValue;
- }
-
- /// <summary>
- /// Read memory out of the target process.
- /// </summary>
- /// <param name="address">The address of memory to read</param>
- /// <param name="buffer">The buffer to write to</param>
- /// <param name="bytesRequested">The number of bytes to read</param>
- /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
- /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
- public bool ReadMemory(ulong address, byte[] buffer, int bytesRequested, out int bytesRead)
- {
- return ReadMemory(address, new Span<byte>(buffer, 0, bytesRequested), out bytesRead);
- }
-
- /// <summary>
- /// Read memory out of the target process.
- /// </summary>
- /// <param name="address">The address of memory to read</param>
- /// <param name="buffer">The buffer to read memory into</param>
- /// <param name="bytesRequested">The number of bytes to read</param>
- /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
- /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
- public bool ReadMemory(ulong address, IntPtr buffer, int bytesRequested, out int bytesRead)
- {
- unsafe
- {
- return ReadMemory(address, new Span<byte>(buffer.ToPointer(), bytesRequested), out bytesRead);
- }
- }
-
- /// <summary>
- /// Read memory out of the target process.
- /// </summary>
- /// <param name="address">The address of memory to read</param>
- /// <param name="buffer">The buffer to read memory into</param>
- /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
- /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
- public bool ReadMemory(ulong address, Span<byte> buffer, out int bytesRead)
- {
- address &= _ignoreAddressBitsMask;
- int bytesRequested = buffer.Length;
- bytesRead = _dataReader.Read(address, buffer);
-
- // If the read failed or a successful partial read
- if (bytesRequested != bytesRead)
- {
- if (_dataReader.TargetPlatform == OSPlatform.Windows)
- {
- // Check if the memory is in a module and cache it if it is
- if (_memoryCache.ReadMemory(address + (uint)bytesRead, buffer.Slice(bytesRead), out int read))
- {
- bytesRead += read;
- }
- }
- }
- return bytesRead > 0;
- }
-
- /// <summary>
- /// Read memory from a PE module for the memory cache. Finds locally or downloads a module
- /// and "maps" it into the address space. This function can return more than requested which
- /// means the block should not be cached.
- /// </summary>
- /// <param name="address">memory address</param>
- /// <param name="bytesRequested">number of bytes</param>
- /// <returns>bytes read or null if error</returns>
- private byte[] ReadMemoryFromModule(ulong address, int bytesRequested)
- {
- // Check if there is a module that contains the address range being read and map it into the virtual address space.
- foreach (ModuleInfo module in _dataReader.EnumerateModules())
- {
- ulong start = module.ImageBase & _ignoreAddressBitsMask;
- ulong end = start + (ulong)module.IndexFileSize;
- if (address >= start && address < end)
- {
- Trace.TraceInformation("ReadMemory: address {0:X16} size {1:X8} found module {2}", address, bytesRequested, module.FileName);
-
- // We found a module that contains the memory requested. Now find or download the PE image.
- PEReader reader = GetPEReader(module);
- if (reader != null)
- {
- // Read the memory from the PE image.
- int rva = unchecked((int)(address - start));
- try
- {
- byte[] data = null;
-
- int sizeOfHeaders = reader.PEHeaders.PEHeader.SizeOfHeaders;
- if (rva >= 0 && rva < sizeOfHeaders)
- {
- // If the address isn't contained in one of the sections, assume that SOS is reader the PE headers directly.
- Trace.TraceInformation("ReadMemory: rva {0:X8} size {1:X8} in PE Header", rva, bytesRequested);
- data = reader.GetEntireImage().GetReader(rva, bytesRequested).ReadBytes(bytesRequested);
- }
- else
- {
- PEMemoryBlock block = reader.GetSectionData(rva);
- if (block.Length > 0)
- {
- int size = Math.Min(block.Length, bytesRequested);
- data = block.GetReader().ReadBytes(size);
- ApplyRelocations(module, reader, rva, data);
- }
- }
-
- return data;
- }
- catch (Exception ex) when (ex is BadImageFormatException || ex is InvalidOperationException || ex is IOException)
- {
- Trace.TraceError("ReadMemory: exception {0}", ex);
- }
- }
- break;
- }
- }
- return null;
- }
-
- private PEReader GetPEReader(ModuleInfo module)
- {
- if (!_pathToPeReader.TryGetValue(module.FileName, out PEReader reader))
- {
- Stream stream = null;
-
- string downloadFilePath = module.FileName;
- if (!File.Exists(downloadFilePath))
- {
- if (SymbolReader.IsSymbolStoreEnabled())
- {
- SymbolStoreKey key = PEFileKeyGenerator.GetKey(Path.GetFileName(downloadFilePath), (uint)module.IndexTimeStamp, (uint)module.IndexFileSize);
- if (key != null)
- {
- // Now download the module from the symbol server
- downloadFilePath = SymbolReader.GetSymbolFile(key);
- }
- }
- }
-
- if (!string.IsNullOrEmpty(downloadFilePath))
- {
- Trace.TraceInformation("GetPEReader: downloading {0}", downloadFilePath);
- try
- {
- stream = File.OpenRead(downloadFilePath);
- }
- catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException || ex is UnauthorizedAccessException || ex is IOException)
- {
- Trace.TraceError("GetPEReader: exception {0}", ex);
- }
- if (stream != null)
- {
- reader = new PEReader(stream);
- if (reader.PEHeaders == null || reader.PEHeaders.PEHeader == null) {
- reader = null;
- }
- _pathToPeReader.Add(module.FileName, reader);
- }
- }
- }
- return reader;
- }
-
- private void ApplyRelocations(ModuleInfo module, PEReader reader, int dataVA, byte[] data)
- {
- PEMemoryBlock relocations = reader.GetSectionData(".reloc");
- if (relocations.Length > 0)
- {
- ulong baseDelta = module.ImageBase - reader.PEHeaders.PEHeader.ImageBase;
- Trace.TraceInformation("ApplyRelocations: dataVA {0:X8} dataCB {1} baseDelta: {2:X16}", dataVA, data.Length, baseDelta);
-
- BlobReader blob = relocations.GetReader();
- while (blob.RemainingBytes > 0)
- {
- // Read IMAGE_BASE_RELOCATION struct
- int virtualAddress = blob.ReadInt32();
- int sizeOfBlock = blob.ReadInt32();
-
- // Each relocation block covers 4K
- if (dataVA >= virtualAddress && dataVA < (virtualAddress + 4096))
- {
- int entryCount = (sizeOfBlock - 8) / 2; // (sizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD)
- Trace.TraceInformation("ApplyRelocations: reloc VirtualAddress {0:X8} SizeOfBlock {1:X8} entry count {2}", virtualAddress, sizeOfBlock, entryCount);
-
- int relocsApplied = 0;
- for (int e = 0; e < entryCount; e++)
- {
- // Read relocation type/offset
- ushort entry = blob.ReadUInt16();
- if (entry == 0) {
- break;
- }
- var type = (BaseRelocationType)(entry >> 12); // type is 4 upper bits
- int relocVA = virtualAddress + (entry & 0xfff); // offset is 12 lower bits
-
- // Is this relocation in the data?
- if (relocVA >= dataVA && relocVA < (dataVA + data.Length))
- {
- int offset = relocVA - dataVA;
- switch (type)
- {
- case BaseRelocationType.ImageRelBasedAbsolute:
- break;
-
- case BaseRelocationType.ImageRelBasedHighLow:
- {
- uint value = BitConverter.ToUInt32(data, offset);
- value += (uint)baseDelta;
- byte[] source = BitConverter.GetBytes(value);
- Array.Copy(source, 0, data, offset, source.Length);
- break;
- }
- case BaseRelocationType.ImageRelBasedDir64:
- {
- ulong value = BitConverter.ToUInt64(data, offset);
- value += baseDelta;
- byte[] source = BitConverter.GetBytes(value);
- Array.Copy(source, 0, data, offset, source.Length);
- break;
- }
- default:
- Debug.Fail($"ApplyRelocations: invalid relocation type {type}");
- break;
- }
- relocsApplied++;
- }
- }
- Trace.TraceInformation("ApplyRelocations: relocs {0} applied", relocsApplied);
- }
- else
- {
- // Skip to the next relocation block
- blob.Offset += sizeOfBlock - 8;
- }
- }
- }
- }
-
- enum BaseRelocationType
- {
- ImageRelBasedAbsolute = 0,
- ImageRelBasedHigh = 1,
- ImageRelBasedLow = 2,
- ImageRelBasedHighLow = 3,
- ImageRelBasedHighAdj = 4,
- ImageRelBasedDir64 = 10,
- }
- }
-}
--- /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;
+using Microsoft.FileFormats.ELF;
+using Microsoft.FileFormats.MachO;
+using Microsoft.FileFormats.PE;
+using Microsoft.SymbolStore.KeyGenerators;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ public static class MemoryServiceExtensions
+ {
+ /// <summary>
+ /// Returns the mask to remove any sign extension for 32 bit addresses
+ /// </summary>
+ public static ulong SignExtensionMask(this IMemoryService memoryService)
+ {
+ return memoryService.PointerSize == 4 ? uint.MaxValue : ulong.MaxValue;
+ }
+
+ /// <summary>
+ /// Read memory out of the target process.
+ /// </summary>
+ /// <param name="memoryService">memory service instance</param>
+ /// <param name="address">The address of memory to read</param>
+ /// <param name="buffer">The buffer to write to</param>
+ /// <param name="bytesRequested">The number of bytes to read</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
+ public static bool ReadMemory(this IMemoryService memoryService, ulong address, byte[] buffer, int bytesRequested, out int bytesRead)
+ {
+ return memoryService.ReadMemory(address, new Span<byte>(buffer, 0, bytesRequested), out bytesRead);
+ }
+
+ /// <summary>
+ /// Read memory out of the target process.
+ /// </summary>
+ /// <param name="memoryService">memory service instance</param>
+ /// <param name="address">The address of memory to read</param>
+ /// <param name="buffer">The buffer to read memory into</param>
+ /// <param name="bytesRequested">The number of bytes to read</param>
+ /// <param name="bytesRead">The number of bytes actually read out of the target process</param>
+ /// <returns>true if any bytes were read at all, false if the read failed (and no bytes were read)</returns>
+ public static bool ReadMemory(this IMemoryService memoryService, ulong address, IntPtr buffer, int bytesRequested, out int bytesRead)
+ {
+ unsafe
+ {
+ return memoryService.ReadMemory(address, new Span<byte>(buffer.ToPointer(), bytesRequested), out bytesRead);
+ }
+ }
+
+ /// <summary>
+ /// Read a 32 bit value from the memory location
+ /// </summary>
+ /// <param name="memoryService">memory service instance</param>
+ /// <param name="address">address to read</param>
+ /// <param name="value">returned value</param>
+ /// <returns>true success, false failure</returns>
+ public static bool ReadDword(this IMemoryService memoryService, ulong address, out uint value)
+ {
+ Span<byte> buffer = stackalloc byte[sizeof(uint)];
+ if (memoryService.ReadMemory(address, buffer, out int bytesRead))
+ {
+ if (bytesRead == sizeof(uint))
+ {
+ value = BitConverter.ToUInt32(buffer.ToArray(), 0);
+ return true;
+ }
+ }
+ value = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Return a pointer sized value from the address.
+ /// </summary>
+ /// <param name="memoryService">memory service instance</param>
+ /// <param name="address">address to read</param>
+ /// <param name="value">returned value</param>
+ /// <returns>true success, false failure</returns>
+ public static bool ReadPointer(this IMemoryService memoryService, ulong address, out ulong value)
+ {
+ int pointerSize = memoryService.PointerSize;
+ Span<byte> buffer = stackalloc byte[pointerSize];
+ if (memoryService.ReadMemory(address, buffer, out int bytesRead))
+ {
+ switch (pointerSize)
+ {
+ case 4:
+ value = BitConverter.ToUInt32(buffer.ToArray(), 0);
+ return true;
+ case 8:
+ value = BitConverter.ToUInt64(buffer.ToArray(), 0);
+ return true;
+ }
+ }
+ value = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Creates a key generator for the runtime module pointed to by the address/size.
+ /// </summary>
+ /// <param name="memoryService">memory service instance</param>
+ /// <param name="config">Target configuration: Windows, Linux or OSX</param>
+ /// <param name="moduleFilePath">module path</param>
+ /// <param name="address">module base address</param>
+ /// <param name="size">module size</param>
+ /// <returns>KeyGenerator or null if error</returns>
+ public static KeyGenerator GetKeyGenerator(this IMemoryService memoryService, OSPlatform config, string moduleFilePath, ulong address, ulong size)
+ {
+ Stream stream = memoryService.CreateMemoryStream(address, size);
+ KeyGenerator generator = null;
+ if (config == OSPlatform.Linux)
+ {
+ var elfFile = new ELFFile(new StreamAddressSpace(stream), 0, true);
+ generator = new ELFFileKeyGenerator(Tracer.Instance, elfFile, moduleFilePath);
+ }
+ else if (config == OSPlatform.OSX)
+ {
+ var machOFile = new MachOFile(new StreamAddressSpace(stream), 0, true);
+ generator = new MachOFileKeyGenerator(Tracer.Instance, machOFile, moduleFilePath);
+ }
+ else if (config == OSPlatform.Windows)
+ {
+ var peFile = new PEFile(new StreamAddressSpace(stream), true);
+ generator = new PEFileKeyGenerator(Tracer.Instance, peFile, moduleFilePath);
+ }
+ else
+ {
+ Trace.TraceError("GetKeyGenerator: unsupported platform {0}", config);
+ }
+ return generator;
+ }
+
+ /// <summary>
+ /// Create a stream for the address range.
+ /// </summary>
+ /// <param name="memoryService">memory service instance</param>
+ /// <param name="address">address to read</param>
+ /// <param name="size">size of stream</param>
+ /// <returns></returns>
+ public static Stream CreateMemoryStream(this IMemoryService memoryService, ulong address, ulong size)
+ {
+ return new TargetStream(memoryService, address, size);
+ }
+
+ /// <summary>
+ /// Stream implementation to read debugger target memory for in-memory PDBs
+ /// </summary>
+ class TargetStream : Stream
+ {
+ private readonly ulong _address;
+ private readonly IMemoryService _memoryService;
+
+ public override long Position { get; set; }
+ public override long Length { get; }
+ public override bool CanSeek { get { return true; } }
+ public override bool CanRead { get { return true; } }
+ public override bool CanWrite { get { return false; } }
+
+ public TargetStream(IMemoryService memoryService, ulong address, ulong size)
+ : base()
+ {
+ Debug.Assert(address != 0);
+ Debug.Assert(size != 0);
+ _memoryService = memoryService;
+ _address = address;
+ Length = (long)size;
+ Position = 0;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (Position + count > Length) {
+ throw new ArgumentOutOfRangeException();
+ }
+ if (_memoryService.ReadMemory(_address + (ulong)Position, new Span<byte>(buffer, offset, count), out int bytesRead)) {
+ Position += bytesRead;
+ }
+ return bytesRead;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ switch (origin) {
+ case SeekOrigin.Begin:
+ Position = offset;
+ break;
+ case SeekOrigin.End:
+ Position = Length + offset;
+ break;
+ case SeekOrigin.Current:
+ Position += offset;
+ break;
+ }
+ return Position;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="$(MicrosoftDiagnosticsRuntimeVersion)" />
- <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\SOS\SOS.NETCore\SOS.NETCore.csproj" />
+ <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="$(MicrosoftDiagnosticsRuntimeVersion)" />
+ <PackageReference Include="Microsoft.SymbolStore" Version="$(MicrosoftSymbolStoreVersion)" />
</ItemGroup>
</Project>
+++ /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 class ServiceProvider : IServiceProvider
- {
- readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
- readonly Dictionary<Type, Func<object>> _factories = new Dictionary<Type, Func<object>>();
-
- /// <summary>
- /// Create a service provider instance
- /// </summary>
- public ServiceProvider()
- {
- }
-
- /// <summary>
- /// Add service factory
- /// </summary>
- /// <param name="type">service type</param>
- /// <param name="factory">function to create service instance</param>
- public void AddServiceFactory(Type type, Func<object> factory) => _factories.Add(type, 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>
- /// 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();
- _services.Add(type, service);
- }
- }
- return service;
- }
- }
-
- public static class ServiceProviderExtensions
- {
- /// <summary>
- /// Returns the instance of the service or returns null if service doesn't exist
- /// </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));
- }
- }
-}
--- /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 static class ServiceProviderExtensions
+ {
+ /// <summary>
+ /// Returns the instance of the service or returns null if service doesn't exist
+ /// </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));
+ }
+ }
+}
--- /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.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ public static class TargetExtensions
+ {
+ /// <summary>
+ /// Returns the decorated platform specific module name. "coreclr" becomes "coreclr.dll"
+ /// for Windows targets, "libcoreclr.so" for Linux targets, etc.
+ /// </summary>
+ /// <param name="target">target instance</param>
+ /// <param name="moduleName">base module name</param>
+ /// <returns>platform module name</returns>
+ public static string GetPlatformModuleName(this ITarget target, string moduleName)
+ {
+ if (target.OperatingSystem == OSPlatform.Windows)
+ {
+ return moduleName + ".dll";
+ }
+ else if (target.OperatingSystem == OSPlatform.Linux)
+ {
+ return "lib" + moduleName + ".so";
+ }
+ else if (target.OperatingSystem == OSPlatform.OSX)
+ {
+ return "lib" + moduleName + ".dylib";
+ }
+ else throw new PlatformNotSupportedException(target.OperatingSystem.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.
-
-namespace Microsoft.Diagnostics.DebugServices
-{
- /// <summary>
- /// Details about a native thread
- /// </summary>
- public struct ThreadInfo
- {
- public readonly int ThreadIndex;
- public readonly uint ThreadId;
- public readonly ulong ThreadTeb;
-
- public ThreadInfo(int index, uint id, ulong teb)
- {
- ThreadIndex = index;
- ThreadId = id;
- ThreadTeb = teb;
- }
- }
-}
+++ /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 Microsoft.Diagnostics.Runtime.DataReaders.Implementation;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using Architecture = Microsoft.Diagnostics.Runtime.Architecture;
-
-namespace Microsoft.Diagnostics.DebugServices
-{
- /// <summary>
- /// Provides thread and register info and values for the clrmd IDataReader
- /// </summary>
- public class ThreadService : IThreadService
- {
- private readonly IDataReader _dataReader;
- private readonly IThreadReader _threadReader;
- private readonly int _contextSize;
- private readonly uint _contextFlags;
- private readonly int _instructionPointerIndex;
- private readonly int _framePointerIndex;
- private readonly int _stackPointerIndex;
- private readonly Dictionary<string, RegisterInfo> _lookupByName;
- private readonly Dictionary<int, RegisterInfo> _lookupByIndex;
- private readonly IEnumerable<RegisterInfo> _registers;
- private readonly Dictionary<uint, byte[]> _threadContextCache = new Dictionary<uint, byte[]>();
- private IEnumerable<ThreadInfo> _threadInfos;
-
- public ThreadService(IDataReader dataReader)
- {
- _dataReader = dataReader;
- _threadReader = (IThreadReader)dataReader;
-
- Type contextType;
- switch (dataReader.Architecture)
- {
- case Architecture.Amd64:
- // Dumps generated with newer dbgeng have bigger context buffers and clrmd requires the context size to at least be that size.
- _contextSize = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0x700 : AMD64Context.Size;
- _contextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments | AMD64Context.ContextFloatingPoint;
- contextType = typeof(AMD64Context);
- break;
-
- case Architecture.X86:
- _contextSize = X86Context.Size;
- _contextFlags = X86Context.ContextControl | X86Context.ContextInteger | X86Context.ContextSegments | X86Context.ContextFloatingPoint;
- contextType = typeof(X86Context);
- break;
-
- case Architecture.Arm64:
- _contextSize = Arm64Context.Size;
- _contextFlags = Arm64Context.ContextControl | Arm64Context.ContextInteger | Arm64Context.ContextFloatingPoint;
- contextType = typeof(Arm64Context);
- break;
-
- case Architecture.Arm:
- _contextSize = ArmContext.Size;
- _contextFlags = ArmContext.ContextControl | ArmContext.ContextInteger | ArmContext.ContextFloatingPoint;
- contextType = typeof(ArmContext);
- break;
-
- default:
- throw new PlatformNotSupportedException($"Unsupported architecture: {dataReader.Architecture}");
- }
-
- var registers = new List<RegisterInfo>();
- int index = 0;
-
- 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) {
- continue;
- }
- RegisterType registerType = registerAttribute.RegisterType & RegisterType.TypeMask;
- switch (registerType)
- {
- case RegisterType.Control:
- case RegisterType.General:
- case RegisterType.Segments:
- break;
- default:
- continue;
- }
- if ((registerAttribute.RegisterType & RegisterType.ProgramCounter) != 0) {
- _instructionPointerIndex = index;
- }
- if ((registerAttribute.RegisterType & RegisterType.StackPointer) != 0) {
- _stackPointerIndex = index;
- }
- if ((registerAttribute.RegisterType & RegisterType.FramePointer) != 0) {
- _framePointerIndex = index;
- }
- FieldOffsetAttribute offsetAttribute = field.GetCustomAttributes<FieldOffsetAttribute>(inherit: false).Single();
- var registerInfo = new RegisterInfo(index, offsetAttribute.Value, Marshal.SizeOf(field.FieldType), registerAttribute.Name ?? field.Name.ToLower());
- registers.Add(registerInfo);
- index++;
- }
-
- _lookupByName = registers.ToDictionary((info) => info.RegisterName);
- _lookupByIndex = registers.ToDictionary((info) => info.RegisterIndex);
- _registers = registers;
- }
-
- /// <summary>
- /// Flush the register service
- /// </summary>
- public void Flush()
- {
- _threadContextCache.Clear();
- }
-
- /// <summary>
- /// Details on all the supported registers
- /// </summary>
- IEnumerable<RegisterInfo> IThreadService.Registers { get { return _registers; } }
-
- /// <summary>
- /// The instruction pointer register index
- /// </summary>
- int IThreadService.InstructionPointerIndex { get { return _instructionPointerIndex; } }
-
- /// <summary>
- /// The frame pointer register index
- /// </summary>
- int IThreadService.FramePointerIndex { get { return _framePointerIndex; } }
-
- /// <summary>
- /// The stack pointer register index
- /// </summary>
- int IThreadService.StackPointerIndex { get { return _stackPointerIndex; } }
-
- /// <summary>
- /// Return the register index for the register name
- /// </summary>
- /// <param name="name">register name</param>
- /// <param name="index">returns register index or -1</param>
- /// <returns>true if name found</returns>
- bool IThreadService.GetRegisterIndexByName(string name, out int index)
- {
- if (_lookupByName.TryGetValue(name, out RegisterInfo info))
- {
- index = info.RegisterIndex;
- return true;
- }
- index = int.MaxValue;
- return false;
- }
-
- /// <summary>
- /// Returns the register info (name, offset, size, etc).
- /// </summary>
- /// <param name="index">register index</param>
- /// <param name="info">RegisterInfo</param>
- /// <returns>true if index found</returns>
- bool IThreadService.GetRegisterInfo(int index, out RegisterInfo info)
- {
- return _lookupByIndex.TryGetValue(index, out info);
- }
-
- /// <summary>
- /// Returns the register value for the thread and register index. This function
- /// can only return register values that are 64 bits or less and currently the
- /// clrmd data targets don't return any floating point or larger registers.
- /// </summary>
- /// <param name="threadId">thread id</param>
- /// <param name="index">register index</param>
- /// <param name="value">value returned</param>
- /// <returns>true if value found</returns>
- bool IThreadService.GetRegisterValue(uint threadId, int index, out ulong value)
- {
- value = 0;
-
- if (_lookupByIndex.TryGetValue(index, out RegisterInfo info))
- {
- try
- {
- byte[] threadContext = ((IThreadService)this).GetThreadContext(threadId);
- unsafe
- {
- fixed (byte* ptr = threadContext)
- {
- switch (info.RegisterSize)
- {
- case 1:
- value = *((byte*)(ptr + info.RegisterOffset));
- return true;
- case 2:
- value = *((ushort*)(ptr + info.RegisterOffset));
- return true;
- case 4:
- value = *((uint*)(ptr + info.RegisterOffset));
- return true;
- case 8:
- value = *((ulong*)(ptr + info.RegisterOffset));
- return true;
- }
- }
- }
- }
- catch (DiagnosticsException)
- {
- }
- }
- return false;
- }
-
- /// <summary>
- /// Returns the raw context buffer bytes for the specified thread.
- /// </summary>
- /// <param name="threadId">thread id</param>
- /// <returns>register context</returns>
- /// <exception cref="DiagnosticsException">invalid thread id</exception>
- byte[] IThreadService.GetThreadContext(uint threadId)
- {
- if (_threadContextCache.TryGetValue(threadId, out byte[] threadContext))
- {
- return threadContext;
- }
- else
- {
- threadContext = new byte[_contextSize];
- try
- {
- if (_dataReader.GetThreadContext(threadId, _contextFlags, new Span<byte>(threadContext, 0, _contextSize)))
- {
- _threadContextCache.Add(threadId, threadContext);
- return threadContext;
- }
- }
- catch (ClrDiagnosticsException ex)
- {
- throw new DiagnosticsException(ex.Message, ex);
- }
- }
- throw new DiagnosticsException();
- }
-
- /// <summary>
- /// Enumerate all the native threads
- /// </summary>
- /// <returns>ThreadInfos for all the threads</returns>
- IEnumerable<ThreadInfo> IThreadService.EnumerateThreads()
- {
- if (_threadInfos == null)
- {
- _threadInfos = _threadReader.EnumerateOSThreadIds()
- .OrderBy((uint threadId) => threadId)
- .Select((uint threadId, int threadIndex) => {
- ulong teb = 0;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- try
- {
- teb = _threadReader.GetThreadTeb(threadId);
- }
- catch (NotImplementedException)
- {
- }
- }
- return new ThreadInfo(threadIndex, threadId, teb);
- });
- }
- return _threadInfos;
- }
-
- /// <summary>
- /// Get the thread info from the thread index
- /// </summary>
- /// <param name="threadIndex">index</param>
- /// <returns>thread info</returns>
- /// <exception cref="DiagnosticsException">invalid thread index</exception>
- ThreadInfo IThreadService.GetThreadInfoFromIndex(int threadIndex)
- {
- try
- {
- return ((IThreadService)this).EnumerateThreads().ElementAt(threadIndex);
- }
- catch (ArgumentOutOfRangeException ex)
- {
- throw new DiagnosticsException($"Invalid thread index: {threadIndex}", ex);
- }
- }
-
- /// <summary>
- /// Get the thread info from the OS thread id
- /// </summary>
- /// <param name="threadId">os id</param>
- /// <returns>thread info</returns>
- /// <exception cref="DiagnosticsException">invalid thread id</exception>
- ThreadInfo IThreadService.GetThreadInfoFromId(uint threadId)
- {
- try
- {
- return ((IThreadService)this).EnumerateThreads().First((ThreadInfo info) => info.ThreadId == threadId);
- }
- catch (InvalidOperationException ex)
- {
- throw new DiagnosticsException($"Invalid thread id: {threadId}", ex);
- }
- }
- }
-}
--- /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.Diagnostics;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+ /// <summary>
+ /// Simple trace/logging support.
+ /// </summary>
+ public sealed class Tracer : Microsoft.SymbolStore.ITracer
+ {
+ public static Microsoft.SymbolStore.ITracer Instance { get; } = new Tracer();
+
+ private Tracer()
+ {
+ }
+
+ public void WriteLine(string message)
+ {
+ Trace.WriteLine(message);
+ Trace.Flush();
+ }
+
+ public void WriteLine(string format, params object[] arguments)
+ {
+ WriteLine(string.Format(format, arguments));
+ }
+
+ public void Information(string message)
+ {
+ Trace.TraceInformation(message);
+ Trace.Flush();
+ }
+
+ public void Information(string format, params object[] arguments)
+ {
+ Trace.TraceInformation(format, arguments);
+ Trace.Flush();
+ }
+
+ public void Warning(string message)
+ {
+ Trace.TraceWarning(message);
+ Trace.Flush();
+ }
+
+ public void Warning(string format, params object[] arguments)
+ {
+ Trace.TraceWarning(format, arguments);
+ Trace.Flush();
+ }
+
+ public void Error(string message)
+ {
+ Trace.TraceError(message);
+ Trace.Flush();
+ }
+
+ public void Error(string format, params object[] arguments)
+ {
+ Trace.TraceError(format, arguments);
+ Trace.Flush();
+ }
+
+ public void Verbose(string message)
+ {
+ Information(message);
+ }
+
+ public void Verbose(string format, params object[] arguments)
+ {
+ Information(format, arguments);
+ }
+ }
+}
+++ /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.Repl
-{
- /// <summary>
- /// OS Platforms to add command
- /// </summary>
- [Flags]
- public enum CommandPlatform : byte
- {
- All = 0x00,
- Windows = 0x01,
- Linux = 0x02,
- OSX = 0x04,
- }
-
- /// <summary>
- /// Base command, option and argument class.
- /// </summary>
- public class BaseAttribute : Attribute
- {
- /// <summary>
- /// Name of the command
- /// </summary>
- public string Name;
-
- /// <summary>
- /// Displayed in the help for the command
- /// </summary>
- public string Help;
- }
-
- /// <summary>
- /// Base command and command alias class.
- /// </summary>
- public class CommandBaseAttribute : BaseAttribute
- {
- /// <summary>
- /// Optional OS platform for the command
- /// </summary>
- public CommandPlatform Platform = CommandPlatform.All;
- }
-
- /// <summary>
- /// Marks the class as a Command.
- /// </summary>
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
- public class CommandAttribute : CommandBaseAttribute
- {
- /// <summary>
- /// Sets the value of the CommandBase.AliasExpansion when the command is executed.
- /// </summary>
- public string AliasExpansion;
- }
-
- /// <summary>
- /// Adds an alias to the previous command attribute
- /// </summary>
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
- public class CommandAliasAttribute : CommandBaseAttribute
- {
- }
-
- /// <summary>
- /// Marks the property as a Option.
- /// </summary>
- [AttributeUsage(AttributeTargets.Property)]
- public class OptionAttribute : BaseAttribute
- {
- }
-
- /// <summary>
- /// Adds an alias to the Option. Help is ignored.
- /// </summary>
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
- public class OptionAliasAttribute : BaseAttribute
- {
- }
-
- /// <summary>
- /// Marks the property the command Argument.
- /// </summary>
- [AttributeUsage(AttributeTargets.Property)]
- public class ArgumentAttribute : BaseAttribute
- {
- }
-
- /// <summary>
- /// Marks the function to invoke to execute the command.
- /// </summary>
- [AttributeUsage(AttributeTargets.Method)]
- public class CommandInvokeAttribute : Attribute
- {
- }
-
- /// <summary>
- /// Marks the function to invoke to display alternate help for command.
- /// </summary>
- [AttributeUsage(AttributeTargets.Method)]
- public class HelpInvokeAttribute : Attribute
- {
- }
-}
+++ /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;
-using System.CommandLine;
-using System.CommandLine.Invocation;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Repl
-{
- /// <summary>
- /// The common command context
- /// </summary>
- public abstract class CommandBase
- {
- /// <summary>
- /// Parser invocation context. Contains the ParseResult, CommandResult, etc. Is null when
- /// InvokeAdditionalHelp is called.
- /// </summary>
- public InvocationContext InvocationContext { get; set; }
-
- /// <summary>
- /// Console service
- /// </summary>
- public IConsoleService Console { get; set; }
-
- /// <summary>
- /// The AliasExpansion value from the CommandAttribute or null if none.
- /// </summary>
- public string AliasExpansion { get; set; }
-
- /// <summary>
- /// Execute the command
- /// </summary>
- [CommandInvoke]
- public abstract void Invoke();
-
- /// <summary>
- /// Display text
- /// </summary>
- /// <param name="message">text message</param>
- protected void Write(string message)
- {
- Console.Write(message);
- }
-
- /// <summary>
- /// Display line
- /// </summary>
- /// <param name="message">line message</param>
- protected void WriteLine(string message)
- {
- Console.Write(message + Environment.NewLine);
- }
-
- /// <summary>
- /// Display formatted text
- /// </summary>
- /// <param name="format">format string</param>
- /// <param name="args">arguments</param>
- protected void WriteLine(string format, params object[] args)
- {
- Console.Write(string.Format(format, args) + Environment.NewLine);
- }
-
- /// <summary>
- /// Display formatted error text
- /// </summary>
- /// <param name="format">format string</param>
- /// <param name="args">arguments</param>
- protected void WriteLineError(string format, params object[] args)
- {
- Console.WriteError(string.Format(format, args) + Environment.NewLine);
- }
-
- /// <summary>
- /// Convert hexadecimal string address into ulong
- /// </summary>
- /// <param name="addressInHexa">0x12345678 or 000012345670 format are supported</param>
- /// <param name="address">parsed hexadecimal address</param>
- /// <returns></returns>
- protected bool TryParseAddress(string addressInHexa, out ulong address)
- {
- // skip 0x or leading 0000 if needed
- if (addressInHexa.StartsWith("0x"))
- addressInHexa = addressInHexa.Substring(2);
- addressInHexa = addressInHexa.TrimStart('0');
-
- return ulong.TryParse(addressInHexa, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out address);
- }
- }
-}
\ No newline at end of file
// 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;
using System.Collections.Generic;
using System.CommandLine;
namespace Microsoft.Diagnostics.Repl
{
- public class CommandProcessor
+ /// <summary>
+ /// Implements the ICommandService interface using System.CommandLine.
+ /// </summary>
+ public class CommandProcessor : ICommandService
{
- private readonly Parser _parser;
- private readonly Command _rootCommand;
- private readonly IServiceProvider _serviceProvider;
- private readonly IConsole _console;
- private readonly Dictionary<string, Handler> _commandHandlers = new Dictionary<string, Handler>();
+ private Parser _parser;
+ private readonly CommandLineBuilder _rootBuilder;
+ private readonly Dictionary<string, CommandHandler> _commandHandlers = new Dictionary<string, CommandHandler>();
/// <summary>
/// Create an instance of the command processor;
/// </summary>
- /// <param name="serviceProvider">service provider interface</param>
- /// <param name="console">console instance</param>
- /// <param name="assemblies">Optional list of assemblies to look for commands</param>
- /// <param name="types">Optional list of types to look for commands</param>
- public CommandProcessor(IServiceProvider serviceProvider, IConsole console, IEnumerable<Assembly> assemblies = null, IEnumerable<Type> types = null)
+ public CommandProcessor()
{
- Debug.Assert(serviceProvider != null);
- Debug.Assert(assemblies != null);
- _serviceProvider = serviceProvider;
- _console = console;
-
- var rootBuilder = new CommandLineBuilder(new Command(">"));
- rootBuilder.UseHelpBuilder((bindingContext) => GetService<IHelpBuilder>())
- .UseParseDirective()
- .UseSuggestDirective()
- .UseParseErrorReporting()
- .UseExceptionHandler(OnException);
-
- if (assemblies != null) {
- BuildCommands(rootBuilder, assemblies);
- }
- if (types != null) {
- BuildCommands(rootBuilder, types);
- }
- _rootCommand = rootBuilder.Command;
- _parser = rootBuilder.Build();
+ _rootBuilder = new CommandLineBuilder(new Command(">"));
+ _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false));
}
- private void OnException(Exception ex, InvocationContext context)
+ /// <summary>
+ /// Parse and execute the command line.
+ /// </summary>
+ /// <param name="commandLine">command line text</param>
+ /// <param name="services">services for the command</param>
+ /// <returns>exit code</returns>
+ public int Execute(string commandLine, IServiceProvider services)
{
- if (ex is NullReferenceException ||
- ex is ArgumentException ||
- ex is ArgumentNullException ||
- ex is ArgumentOutOfRangeException ||
- ex is NotImplementedException) {
- context.Console.Error.WriteLine(ex.ToString());
+ Debug.Assert(_parser != null);
+
+ // Parse the command line and invoke the command
+ ParseResult parseResult = _parser.Parse(commandLine);
+
+ var context = new InvocationContext(parseResult, new LocalConsole(services));
+ if (parseResult.Errors.Count > 0)
+ {
+ context.InvocationResult = new ParseErrorResult();
}
- else {
- context.Console.Error.WriteLine(ex.Message);
+ else
+ {
+ if (parseResult.CommandResult.Command is Command command)
+ {
+ if (command.Handler is CommandHandler handler)
+ {
+ ITarget target = services.GetService<ITarget>();
+ if (!handler.IsValidPlatform(target))
+ {
+ context.Console.Error.WriteLine($"Command '{command.Name}' not supported on this target");
+ return 1;
+ }
+ try
+ {
+ handler.Invoke(context, services);
+ }
+ catch (Exception ex)
+ {
+ OnException(ex, context);
+ }
+ }
+ }
}
- Trace.TraceError(ex.ToString());
+
+ context.InvocationResult?.Apply(context);
+ return context.ResultCode;
}
/// <summary>
- /// Creates a new instance of the command help builder
+ /// Displays the help for a command
/// </summary>
- public IHelpBuilder CreateHelpBuilder()
+ /// <param name="commandName">name of the command or alias</param>
+ /// <param name="services">service provider</param>
+ /// <returns>true if success, false if command not found</returns>
+ public bool DisplayHelp(string commandName, IServiceProvider services)
{
- return new LocalHelpBuilder(this);
+ Command command = null;
+ if (!string.IsNullOrEmpty(commandName))
+ {
+ command = _rootBuilder.Command.Children.OfType<Command>().FirstOrDefault((cmd) => commandName == cmd.Name || cmd.Aliases.Any((alias) => commandName == alias));
+ if (command == null)
+ {
+ return false;
+ }
+ if (command.Handler is CommandHandler handler)
+ {
+ ITarget target = services.GetService<ITarget>();
+ if (!handler.IsValidPlatform(target))
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ ITarget target = services.GetService<ITarget>();
+
+ // Create temporary builder adding only the commands that are valid for the target
+ var builder = new CommandLineBuilder(new Command(">"));
+ foreach (Command cmd in _rootBuilder.Command.Children.OfType<Command>())
+ {
+ if (cmd.Handler is CommandHandler handler)
+ {
+ if (handler.IsValidPlatform(target))
+ {
+ builder.AddCommand(cmd);
+ }
+ }
+ }
+ command = builder.Command;
+ }
+ Debug.Assert(command != null);
+ IHelpBuilder helpBuilder = new LocalHelpBuilder(this, new LocalConsole(services), useHelpBuilder: true);
+ helpBuilder.Write(command);
+ return true;
}
/// <summary>
- /// Parse the command line.
+ /// Enumerates all the command's name and help
/// </summary>
- /// <param name="commandLine">command line txt</param>
- /// <returns>exit code</returns>
- public Task<int> Parse(string commandLine)
- {
- return _parser.InvokeAsync(commandLine, _console);
- }
+ public IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases));
/// <summary>
- /// Display all the help or a specific command's help.
+ /// Add the commands and aliases attributes found in the type.
/// </summary>
- /// <param name="name">command name or null</param>
- /// <returns>command instance or null if not found</returns>
- public Command GetCommand(string name)
- {
- if (string.IsNullOrEmpty(name)) {
- return _rootCommand;
- }
- else {
- return _rootCommand.Children.OfType<Command>().FirstOrDefault((cmd) => name == cmd.Name || cmd.Aliases.Any((alias) => name == alias));
- }
- }
-
- private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable<Assembly> assemblies)
- {
- BuildCommands(rootBuilder, assemblies.SelectMany((assembly) => assembly.GetExportedTypes()));
- }
-
- private void BuildCommands(CommandLineBuilder rootBuilder, IEnumerable<Type> types)
+ /// <param name="type">Command type to search</param>
+ /// <param name="factory">function to create command instance</param>
+ public void AddCommands(Type type, Func<IServiceProvider, object> factory)
{
- foreach (Type type in types)
+ for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
{
- for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
+ if (baseType == typeof(CommandBase)) {
+ break;
+ }
+ var commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false);
+ foreach (CommandAttribute commandAttribute in commandAttributes)
{
- if (baseType == typeof(CommandBase)) {
- break;
+ if (factory == null)
+ {
+ // Assumes zero parameter constructor
+ ConstructorInfo constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ??
+ throw new ArgumentException($"No eligible constructor found in {type}");
+
+ factory = (services) => constructor.Invoke(Array.Empty<object>());
}
- BuildCommands(rootBuilder, baseType);
+ CreateCommand(baseType, commandAttribute, factory);
}
}
+
+ // Build parser instance after all the commands and aliases are added
+ BuildParser();
}
- private void BuildCommands(CommandLineBuilder rootBuilder, Type type)
+ private void CreateCommand(Type type, CommandAttribute commandAttribute, Func<IServiceProvider, object> factory)
{
- Command command = null;
+ var command = new Command(commandAttribute.Name, commandAttribute.Help);
+ var properties = new List<(PropertyInfo, Option)>();
+ var arguments = new List<(PropertyInfo, Argument)>();
- var baseAttributes = (BaseAttribute[])type.GetCustomAttributes(typeof(BaseAttribute), inherit: false);
- foreach (BaseAttribute baseAttribute in baseAttributes)
+ foreach (string alias in commandAttribute.Aliases)
{
- if (baseAttribute is CommandAttribute commandAttribute && IsValidPlatform(commandAttribute))
- {
- command = new Command(commandAttribute.Name, commandAttribute.Help);
- var properties = new List<(PropertyInfo, Option)>();
- var arguments = new List<(PropertyInfo, Argument)>();
+ command.AddAlias(alias);
+ }
- foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+ foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+ {
+ var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
+ if (argumentAttribute != null)
+ {
+ IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
+
+ var argument = new Argument {
+ Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
+ Description = argumentAttribute.Help,
+ ArgumentType = property.PropertyType,
+ Arity = arity
+ };
+ command.AddArgument(argument);
+ arguments.Add((property, argument));
+ }
+ else
+ {
+ var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
+ if (optionAttribute != null)
{
- var argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
- if (argumentAttribute != null)
- {
- IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
-
- var argument = new Argument {
- Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
- Description = argumentAttribute.Help,
- ArgumentType = property.PropertyType,
- Arity = arity
- };
- command.AddArgument(argument);
- arguments.Add((property, argument));
- }
- else
+ var option = new Option(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help) {
+ Argument = new Argument { ArgumentType = property.PropertyType }
+ };
+ command.AddOption(option);
+ properties.Add((property, option));
+
+ foreach (string alias in optionAttribute.Aliases)
{
- var optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
- if (optionAttribute != null)
- {
- var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help) {
- Argument = new Argument { ArgumentType = property.PropertyType }
- };
- command.AddOption(option);
- properties.Add((property, option));
-
- foreach (var optionAliasAttribute in (OptionAliasAttribute[])property.GetCustomAttributes(typeof(OptionAliasAttribute), inherit: false))
- {
- option.AddAlias(optionAliasAttribute.Name);
- }
- }
- else
- {
- // If not an option, add as just a settable properties
- properties.Add((property, null));
- }
+ option.AddAlias(alias);
}
}
-
- var handler = new Handler(this, commandAttribute.AliasExpansion, arguments, properties, type);
- _commandHandlers.Add(command.Name, handler);
- command.Handler = handler;
- rootBuilder.AddCommand(command);
- }
-
- if (baseAttribute is CommandAliasAttribute commandAliasAttribute && IsValidPlatform(commandAliasAttribute))
- {
- if (command == null)
+ else
{
- throw new ArgumentException($"No previous CommandAttribute for this CommandAliasAttribute: {type.Name}");
+ // If not an option, add as just a settable properties
+ properties.Add((property, null));
}
- command.AddAlias(commandAliasAttribute.Name);
}
}
- }
- private object GetService(Type serviceType)
- {
- return _serviceProvider.GetService(serviceType);
+ var handler = new CommandHandler(commandAttribute, arguments, properties, type, factory);
+ _commandHandlers.Add(command.Name, handler);
+ command.Handler = handler;
+ _rootBuilder.AddCommand(command);
}
- private T GetService<T>()
+ private void BuildParser()
{
- T service = (T)_serviceProvider.GetService(typeof(T));
- Debug.Assert(service != null);
- return service;
+ _parser = _rootBuilder.Build();
}
- /// <summary>
- /// Returns true if the command should be added.
- /// </summary>
- private static bool IsValidPlatform(CommandBaseAttribute attribute)
+ private void OnException(Exception ex, InvocationContext context)
{
- if (attribute.Platform != CommandPlatform.All)
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return (attribute.Platform & CommandPlatform.Windows) != 0;
- }
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return (attribute.Platform & CommandPlatform.Linux) != 0;
- }
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return (attribute.Platform & CommandPlatform.OSX) != 0;
- }
+ if (ex is NullReferenceException ||
+ ex is ArgumentException ||
+ ex is ArgumentNullException ||
+ ex is ArgumentOutOfRangeException ||
+ ex is NotImplementedException) {
+ context.Console.Error.WriteLine(ex.ToString());
}
- return true;
+ else {
+ context.Console.Error.WriteLine(ex.Message);
+ }
+ Trace.TraceError(ex.ToString());
}
- private static string BuildAlias(string parameterName)
+ private static string BuildOptionAlias(string parameterName)
{
if (string.IsNullOrWhiteSpace(parameterName)) {
throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName));
return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}";
}
- class Handler : ICommandHandler
+ /// <summary>
+ /// The normal command handler.
+ /// </summary>
+ class CommandHandler : ICommandHandler
{
- private readonly CommandProcessor _commandProcessor;
- private readonly string _aliasExpansion;
+ private readonly CommandAttribute _commandAttribute;
private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments;
private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties;
- private readonly ConstructorInfo _constructor;
+ private readonly Func<IServiceProvider, object> _factory;
private readonly MethodInfo _methodInfo;
private readonly MethodInfo _methodInfoHelp;
- public Handler(CommandProcessor commandProcessor, string aliasExpansion, IEnumerable<(PropertyInfo, Argument)> arguments, IEnumerable<(PropertyInfo, Option)> properties, Type type)
+ public CommandHandler(
+ CommandAttribute commandAttribute,
+ IEnumerable<(PropertyInfo, Argument)> arguments,
+ IEnumerable<(PropertyInfo, Option)> properties,
+ Type type,
+ Func<IServiceProvider, object> factory)
{
- _commandProcessor = commandProcessor;
- _aliasExpansion = aliasExpansion;
+ _commandAttribute = commandAttribute;
_arguments = arguments;
_properties = properties;
-
- _constructor = type.GetConstructors().SingleOrDefault((info) => info.GetParameters().Length == 0) ??
- throw new ArgumentException($"No eligible constructor found in {type}");
+ _factory = factory;
_methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<CommandInvokeAttribute>() != null).SingleOrDefault() ??
throw new ArgumentException($"No command invoke method found in {type}");
Task<int> ICommandHandler.InvokeAsync(InvocationContext context)
{
- try
+ return Task.FromException<int>(new NotImplementedException());
+ }
+
+ /// <summary>
+ /// Returns the command name
+ /// </summary>
+ internal string Name => _commandAttribute.Name;
+
+ /// <summary>
+ /// Returns the command's help text
+ /// </summary>
+ internal string Help => _commandAttribute.Help;
+
+ /// <summary>
+ /// Returns the list of the command's aliases.
+ /// </summary>
+ internal IEnumerable<string> Aliases => _commandAttribute.Aliases;
+
+ /// <summary>
+ /// Returns true if the command should be added.
+ /// </summary>
+ internal bool IsValidPlatform(ITarget target)
+ {
+ if ((_commandAttribute.Platform & CommandPlatform.Global) != 0)
{
- Invoke(_methodInfo, context);
+ return true;
}
- catch (Exception ex)
+ if (target != null)
{
- return Task.FromException<int>(ex);
+ if (target.OperatingSystem == OSPlatform.Windows)
+ {
+ return (_commandAttribute.Platform & CommandPlatform.Windows) != 0;
+ }
+ if (target.OperatingSystem == OSPlatform.Linux)
+ {
+ return (_commandAttribute.Platform & CommandPlatform.Linux) != 0;
+ }
+ if (target.OperatingSystem == OSPlatform.OSX)
+ {
+ return (_commandAttribute.Platform & CommandPlatform.OSX) != 0;
+ }
}
- return Task.FromResult(context.ResultCode);
+ return false;
}
+ /// <summary>
+ /// Execute the command synchronously.
+ /// </summary>
+ /// <param name="context">invocation context</param>
+ /// <param name="services">service provider</param>
+ internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services);
+
/// <summary>
/// Executes the command's help invoke function if exists
/// </summary>
+ /// <param name="parser">parser instance</param>
+ /// <param name="services">service provider</param>
/// <returns>true help called, false no help function</returns>
- internal bool InvokeHelp()
+ internal bool InvokeHelp(Parser parser, IServiceProvider services)
{
- if (_methodInfoHelp == null)
- {
+ if (_methodInfoHelp == null) {
return false;
}
// The InvocationContext is null so the options and arguments in the
- // command instance created don't get set. The context for the command
+ // command instance created are not set. The context for the command
// requesting help (either the help command or some other command using
// --help) won't work for the command instance that implements it's own
// help (SOS command).
- Invoke(_methodInfoHelp, context: null);
+ Invoke(_methodInfoHelp, context: null, parser, services);
return true;
}
- private void Invoke(MethodInfo methodInfo, InvocationContext context)
+ private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services)
{
try
{
- // Assumes zero parameter constructor
- object instance = _constructor.Invoke(Array.Empty<object>());
- SetProperties(context, instance);
+ object instance = _factory(services);
+ SetProperties(context, parser, services, instance);
- object[] arguments = BuildArguments(methodInfo, context);
+ object[] arguments = BuildArguments(methodInfo, services);
methodInfo.Invoke(instance, arguments);
}
catch (TargetInvocationException ex)
}
}
- private void SetProperties(InvocationContext context, object instance)
+ private void SetProperties(InvocationContext context, Parser parser, IServiceProvider services, object instance)
{
- IEnumerable<OptionResult> optionResults = context?.ParseResult.CommandResult.Children.OfType<OptionResult>();
+ ParseResult defaultParseResult = null;
+
+ // Parse the default options if any
+ string defaultOptions = _commandAttribute.DefaultOptions;
+ if (defaultOptions != null) {
+ defaultParseResult = parser.Parse(Name + " " + defaultOptions);
+ }
+ // Now initialize the option and service properties from the default and command line options
foreach ((PropertyInfo Property, Option Option) property in _properties)
{
object value = property.Property.GetValue(instance);
- if (property.Property.Name == nameof(CommandBase.AliasExpansion)) {
- value = _aliasExpansion;
- }
- else
+ if (property.Option != null)
{
- Type propertyType = property.Property.PropertyType;
- object service = GetService(propertyType, context);
- if (service != null) {
- value = service;
+ if (defaultParseResult != null)
+ {
+ OptionResult defaultOptionResult = defaultParseResult.FindResultFor(property.Option);
+ if (defaultOptionResult != null) {
+ value = defaultOptionResult.GetValueOrDefault();
+ }
}
- else if (context != null && property.Option != null)
+ if (context != null)
{
- OptionResult optionResult = optionResults.Where((result) => result.Option == property.Option).SingleOrDefault();
+ OptionResult optionResult = context.ParseResult.FindResultFor(property.Option);
if (optionResult != null) {
value = optionResult.GetValueOrDefault();
}
}
}
+ else
+ {
+ Type propertyType = property.Property.PropertyType;
+ object service = services.GetService(propertyType);
+ if (service != null) {
+ value = service;
+ }
+ }
property.Property.SetValue(instance, value);
}
- if (context != null)
+ // Initialize any argument properties from the default and command line arguments
+ foreach ((PropertyInfo Property, Argument Argument) argument in _arguments)
{
- IEnumerable<ArgumentResult> argumentResults = context.ParseResult.CommandResult.Children.OfType<ArgumentResult>();
+ object value = argument.Property.GetValue(instance);
+
+ List<string> array = null;
+ if (argument.Property.PropertyType.IsArray && argument.Property.PropertyType.GetElementType() == typeof(string))
+ {
+ array = new List<string>();
+ if (value is IEnumerable<string> entries) {
+ array.AddRange(entries);
+ }
+ }
- foreach ((PropertyInfo Property, Argument Argument) argument in _arguments)
+ if (defaultParseResult != null)
+ {
+ ArgumentResult defaultArgumentResult = defaultParseResult.FindResultFor(argument.Argument);
+ if (defaultArgumentResult != null)
+ {
+ value = defaultArgumentResult.GetValueOrDefault();
+ if (array != null && value is IEnumerable<string> entries) {
+ array.AddRange(entries);
+ }
+ }
+ }
+ if (context != null)
{
- ArgumentResult argumentResult = argumentResults.Where((result) => result.Argument == argument.Argument).SingleOrDefault();
- if (argumentResult != null)
+ ArgumentResult argumentResult = context.ParseResult.FindResultFor(argument.Argument);
+ if (argumentResult != null)
{
- object value = argumentResult.GetValueOrDefault();
- argument.Property.SetValue(instance, value);
+ value = argumentResult.GetValueOrDefault();
+ if (array != null && value is IEnumerable<string> entries) {
+ array.AddRange(entries);
+ }
}
}
+
+ argument.Property.SetValue(instance, array != null ? array.ToArray() : value);
}
}
- private object[] BuildArguments(MethodBase methodBase, InvocationContext context)
+ private object[] BuildArguments(MethodBase methodBase, IServiceProvider services)
{
ParameterInfo[] parameters = methodBase.GetParameters();
object[] arguments = new object[parameters.Length];
- for (int i = 0; i < parameters.Length; i++) {
+ for (int i = 0; i < parameters.Length; i++)
+ {
Type parameterType = parameters[i].ParameterType;
+
// The parameter will passed as null to allow for "optional" services. The invoked
// method needs to check for possible null parameters.
- arguments[i] = GetService(parameterType, context);
+ arguments[i] = services.GetService(parameterType);
}
return arguments;
}
-
- private object GetService(Type type, InvocationContext context)
- {
- object service;
- if (type == typeof(InvocationContext)) {
- service = context;
- }
- else {
- service = _commandProcessor.GetService(type);
- }
- return service;
- }
}
+ /// <summary>
+ /// Local help builder that allows commands to provide more detailed help
+ /// text via the "InvokeHelp" function.
+ /// </summary>
class LocalHelpBuilder : IHelpBuilder
{
private readonly CommandProcessor _commandProcessor;
+ private readonly IConsole _console;
+ private readonly bool _useHelpBuilder;
- public LocalHelpBuilder(CommandProcessor commandProcessor)
+ public LocalHelpBuilder(CommandProcessor commandProcessor, IConsole console, bool useHelpBuilder)
{
_commandProcessor = commandProcessor;
+ _console = console;
+ _useHelpBuilder = useHelpBuilder;
}
void IHelpBuilder.Write(ICommand command)
{
- if (_commandProcessor._commandHandlers.TryGetValue(command.Name, out Handler handler))
+ bool useHelpBuilder = _useHelpBuilder;
+ if (_commandProcessor._commandHandlers.TryGetValue(command.Name, out CommandHandler handler))
{
- if (handler.InvokeHelp()) {
+ if (handler.InvokeHelp(_commandProcessor._parser, LocalConsole.ToServices(_console))) {
return;
}
+ useHelpBuilder = true;
}
- var helpBuilder = new HelpBuilder(_commandProcessor._console, maxWidth: Console.WindowWidth);
- helpBuilder.Write(command);
+ if (useHelpBuilder)
+ {
+ var helpBuilder = new HelpBuilder(_console, maxWidth: Console.WindowWidth);
+ helpBuilder.Write(command);
+ }
+ }
+ }
+
+ /// <summary>
+ /// This class does two things: wraps the IConsoleService and provides the IConsole interface and
+ /// pipes through the System.CommandLine parsing allowing per command invocation data (service
+ /// provider and raw command line) to be passed through.
+ /// </summary>
+ class LocalConsole : IConsole
+ {
+ public static IServiceProvider ToServices(IConsole console) => ((LocalConsole)console).Services;
+
+ public readonly IServiceProvider Services;
+
+ private readonly IConsoleService _console;
+
+ public LocalConsole(IServiceProvider services)
+ {
+ Services = services;
+ _console = services.GetService<IConsoleService>();
+ Debug.Assert(_console != null);
+ Out = new StandardStreamWriter((text) => _console.Write(text));
+ Error = new StandardStreamWriter((text) => _console.WriteError(text));
}
+
+ #region IConsole
+
+ public IStandardStreamWriter Out { get; }
+
+ bool IStandardOut.IsOutputRedirected { get { return false; } }
+
+ public IStandardStreamWriter Error { get; }
+
+ bool IStandardError.IsErrorRedirected { get { return false; } }
+
+ bool IStandardIn.IsInputRedirected { get { return false; } }
+
+ class StandardStreamWriter : IStandardStreamWriter
+ {
+ readonly Action<string> _write;
+
+ public StandardStreamWriter(Action<string> write) => _write = write;
+
+ void IStandardStreamWriter.Write(string value) => _write(value);
+ }
+
+ #endregion
}
}
}
--- /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;
+using System.CommandLine;
+using System.CommandLine.Help;
+
+namespace Microsoft.Diagnostics.Repl
+{
+ [Command(Name = "help", Help = "Display help for a command.", Platform = CommandPlatform.Global)]
+ public class HelpCommand : CommandBase
+ {
+ [Argument(Help = "Command to find help.")]
+ public string Command { get; set; }
+
+ private readonly CommandProcessor _commandProcessor;
+ private readonly IServiceProvider _services;
+
+ public HelpCommand(CommandProcessor commandProcessor, IServiceProvider services)
+ {
+ _commandProcessor = commandProcessor;
+ _services = services;
+ }
+
+ public override void Invoke()
+ {
+ if (!_commandProcessor.DisplayHelp(Command, _services))
+ {
+ throw new NotSupportedException($"Help for {Command} not found");
+ }
+ }
+ }
+}
using Microsoft.Diagnostics.DebugServices;
using System;
using System.Collections.Generic;
-using System.CommandLine;
-using System.CommandLine.IO;
-using System.IO;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
namespace Microsoft.Diagnostics.Repl
{
- public sealed class ConsoleProvider : IConsole, IConsoleService
+ public sealed class ConsoleProvider : IConsoleService
{
private readonly List<StringBuilder> m_history;
NewOutput(text, errorColor);
});
- Out = new StandardStreamWriter((text) => WriteOutput(OutputType.Normal, text));
- Error = new StandardStreamWriter((text) => WriteOutput(OutputType.Error, text));
-
// Hook ctrl-C and ctrl-break
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCtrlBreakKeyPress);
}
/// Start input processing and command dispatching
/// </summary>
/// <param name="dispatchCommand">Called to dispatch a command on ENTER</param>
- public async Task Start(Func<string, CancellationToken, Task> dispatchCommand)
+ public void Start(Action<string, CancellationToken> dispatchCommand)
{
m_lastCommandLine = null;
m_interactiveConsole = !Console.IsInputRedirected;
if (m_interactiveConsole)
{
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
- await ProcessKeyInfo(keyInfo, dispatchCommand);
+ ProcessKeyInfo(keyInfo, dispatchCommand);
}
else
{
if (string.IsNullOrEmpty(line)) {
continue;
}
- bool result = await Dispatch(line, dispatchCommand);
+ bool result = Dispatch(line, dispatchCommand);
if (!m_shutdown)
{
if (result) {
m_refreshingLine = false;
}
- private async Task ProcessKeyInfo(ConsoleKeyInfo keyInfo, Func<string, CancellationToken, Task> dispatchCommand)
+ private void ProcessKeyInfo(ConsoleKeyInfo keyInfo, Action<string, CancellationToken> dispatchCommand)
{
int activeLineLen = m_activeLine.Length;
}
m_selectedHistory = m_history.Count;
- await Dispatch(newCommand, dispatchCommand);
+ Dispatch(newCommand, dispatchCommand);
SwitchToHistoryEntry();
break;
}
}
- private async Task<bool> Dispatch(string newCommand, Func<string, CancellationToken, Task> dispatchCommand)
+ private bool Dispatch(string newCommand, Action<string, CancellationToken> dispatchCommand)
{
bool result = true;
CommandStarting();
m_interruptExecutingCommand = new CancellationTokenSource();
+ ((IConsoleService)this).CancellationToken = m_interruptExecutingCommand.Token;
try
{
newCommand = newCommand.Trim();
try
{
WriteLine(OutputType.Normal, "{0}{1}", m_prompt, newCommand);
- await dispatchCommand(newCommand, m_interruptExecutingCommand.Token);
+ dispatchCommand(newCommand, m_interruptExecutingCommand.Token);
m_lastCommandLine = newCommand;
}
catch (OperationCanceledException)
}
}
- #region IConsole
-
- public IStandardStreamWriter Out { get; }
-
- bool IStandardOut.IsOutputRedirected { get { return false; } }
-
- public IStandardStreamWriter Error { get; }
-
- bool IStandardError.IsErrorRedirected { get { return false; } }
-
- bool IStandardIn.IsInputRedirected { get { return false; } }
-
- class StandardStreamWriter : IStandardStreamWriter
- {
- readonly Action<string> _write;
-
- public StandardStreamWriter(Action<string> write) => _write = write;
-
- void IStandardStreamWriter.Write(string value) => _write(value);
- }
-
- #endregion
-
#region IConsoleService
void IConsoleService.Write(string text) => WriteOutput(OutputType.Normal, text);
+ void IConsoleService.WriteWarning(string text) => WriteOutput(OutputType.Warning, text);
+
void IConsoleService.WriteError(string text) => WriteOutput(OutputType.Error, text);
- void IConsoleService.Exit() => Stop();
+ CancellationToken IConsoleService.CancellationToken { get; set; }
#endregion
}
--- /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 = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exit interactive mode.", Platform = CommandPlatform.Global)]
+ public class ExitCommand : CommandBase
+ {
+ private readonly Action _exit;
+
+ public ExitCommand(Action exit)
+ {
+ _exit = exit;
+ }
+
+ public override void Invoke()
+ {
+ _exit();
+ }
+ }
+}
using System.IO;
using System.Reflection;
-namespace SOS
+namespace SOS.Hosting
{
/// <summary>
/// Used to enable app-local assembly unification.
// 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 Microsoft.Diagnostics.Runtime;
using Microsoft.Diagnostics.Runtime.Interop;
using Microsoft.Diagnostics.Runtime.Utilities;
using System;
-using System.Globalization;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
-namespace SOS
+namespace SOS.Hosting
{
- internal unsafe class LLDBServices : COMCallableIUnknown
+ internal sealed unsafe class LLDBServices : COMCallableIUnknown
{
private static readonly Guid IID_ILLDBServices = new Guid("2E6C569A-9E14-4DA4-9DFC-CDB73A532566");
private static readonly Guid IID_ILLDBServices2 = new Guid("012F32F0-33BA-4E8E-BC01-037D382D8A5E");
public IntPtr ILLDBServices { get; }
- readonly SOSHost _soshost;
+ private readonly SOSHost _soshost;
/// <summary>
/// Create an instance of the service wrapper SOS uses.
/// </summary>
/// <param name="soshost">SOS host instance</param>
- public LLDBServices(SOSHost soshost)
+ internal LLDBServices(SOSHost soshost)
{
_soshost = soshost;
builder.AddMethod(new GetSourceFileLineOffsetsDelegate(soshost.GetSourceFileLineOffsets));
builder.AddMethod(new FindSourceFileDelegate(soshost.FindSourceFile));
- builder.AddMethod(new GetCurrentProcessIdDelegate(soshost.GetCurrentProcessId));
+ builder.AddMethod(new GetCurrentProcessSystemIdDelegate(soshost.GetCurrentProcessSystemId));
builder.AddMethod(new GetCurrentThreadIdDelegate(soshost.GetCurrentThreadId));
builder.AddMethod(new SetCurrentThreadIdDelegate(soshost.SetCurrentThreadId));
builder.AddMethod(new GetCurrentThreadSystemIdDelegate(soshost.GetCurrentThreadSystemId));
builder.AddMethod(new GetThreadIdBySystemIdDelegate(soshost.GetThreadIdBySystemId));
- builder.AddMethod(new GetThreadContextByIdDelegate(soshost.GetThreadContextById));
+ builder.AddMethod(new GetThreadContextBySystemIdDelegate(soshost.GetThreadContextBySystemId));
builder.AddMethod(new GetValueByNameDelegate(GetValueByName));
builder.AddMethod(new GetInstructionOffsetDelegate(soshost.GetInstructionOffset));
string GetCoreClrDirectory(
IntPtr self)
{
- if (_soshost.AnalyzeContext.RuntimeModuleDirectory == null)
+ IRuntime currentRuntime = _soshost.Target.Services.GetService<IRuntimeService>()?.CurrentRuntime;
+ if (currentRuntime != null)
{
- foreach (ModuleInfo module in _soshost.DataReader.EnumerateModules())
- {
- if (SOSHost.IsCoreClrRuntimeModule(module))
- {
- _soshost.AnalyzeContext.RuntimeModuleDirectory = Path.GetDirectoryName(module.FileName) + Path.DirectorySeparatorChar;
- }
- }
+ return Path.GetDirectoryName(currentRuntime.RuntimeModule.FileName);
}
- return _soshost.AnalyzeContext.RuntimeModuleDirectory;
+ return null;
}
int VirtualUnwind(
bool runtimeOnly,
ModuleLoadCallback callback)
{
- foreach (ModuleInfo module in _soshost.DataReader.EnumerateModules())
+ IEnumerable<IModule> modules;
+ if (runtimeOnly)
{
- if (runtimeOnly)
- {
- if (SOSHost.IsCoreClrRuntimeModule(module))
- {
- callback(IntPtr.Zero, module.FileName, module.ImageBase, module.IndexFileSize);
- break;
- }
- }
- else
- {
- callback(IntPtr.Zero, module.FileName, module.ImageBase, module.IndexFileSize);
- }
+ modules = _soshost.ModuleService.GetModuleFromModuleName(_soshost.Target.GetPlatformModuleName("coreclr"));
+ }
+ else
+ {
+ modules = _soshost.ModuleService.EnumerateModules();
+ }
+ foreach (IModule module in modules)
+ {
+ callback(IntPtr.Zero, module.FileName, module.ImageBase, (uint)module.ImageSize);
}
return HResult.S_OK;
}
{
try
{
- ModuleInfo module = _soshost.DataReader.EnumerateModules().ElementAt((int)index);
- if (module == null) {
- return HResult.E_FAIL;
- }
+ IModule module = _soshost.ModuleService.GetModuleFromIndex((int)index);
SOSHost.Write(moduleBase, module.ImageBase);
- SOSHost.Write(moduleSize, (uint)module.IndexFileSize);
+ SOSHost.Write(moduleSize, module.ImageSize);
}
- catch (ArgumentOutOfRangeException)
+ catch (DiagnosticsException)
{
return HResult.E_FAIL;
}
uint* foundSize);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
- private delegate int GetCurrentProcessIdDelegate(
+ private delegate int GetCurrentProcessSystemIdDelegate(
IntPtr self,
out uint id);
[Out] out uint id);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
- private delegate int GetThreadContextByIdDelegate(
+ private delegate int GetThreadContextBySystemIdDelegate(
IntPtr self,
uint threadId,
uint contextFlags,
/// <summary>
/// The LoadNativeSymbolsDelegate2 callback
/// </summary>
- public delegate void ModuleLoadCallback(
+ private delegate void ModuleLoadCallback(
IntPtr parameter,
[In, MarshalAs(UnmanagedType.LPStr)] string moduleFilePath,
[In] ulong moduleAddress,
- [In] int moduleSize);
+ [In] uint moduleSize);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int LoadNativeSymbolsDelegate2(
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int GetModuleVersionInformationDelegate(
IntPtr self,
- [In] uint Index,
- [In] ulong Base,
- [In][MarshalAs(UnmanagedType.LPStr)] string Item,
- [Out] byte* Buffer,
- [In] uint BufferSize,
- [Out] uint* VerInfoSize);
+ [In] uint moduleIndex,
+ [In] ulong moduleBase,
+ [In][MarshalAs(UnmanagedType.LPStr)] string item,
+ [Out] byte* buffer,
+ [In] uint bufferSize,
+ [Out] uint* verInfoSize);
#endregion
}
-}
\ No newline at end of file
+}
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\SOS.NETCore\SOS.NETCore.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\SOS.InstallHelper\SOS.InstallHelper.csproj" />
- <ProjectReference Include="..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
</ItemGroup>
</Project>
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
+using Architecture = System.Runtime.InteropServices.Architecture;
-namespace SOS
+namespace SOS.Hosting
{
/// <summary>
/// Helper code to hosting SOS under ClrMD
private const string SOSInitialize = "SOSInitializeByHost";
- const string DesktopRuntimeModuleName = "clr";
-
- internal readonly IDataReader DataReader;
- internal readonly AnalyzeContext AnalyzeContext;
-
- private readonly IThreadService _threadService;
- private readonly MemoryService _memoryService;
- private readonly IConsoleService _console;
- private readonly COMCallableIUnknown _ccw;
+ internal readonly ITarget Target;
+ internal readonly IConsoleService ConsoleService;
+ internal readonly IModuleService ModuleService;
+ internal readonly IThreadService ThreadService;
+ internal readonly IMemoryService MemoryService;
+ private readonly ulong _ignoreAddressBitsMask;
private readonly IntPtr _interface;
- private readonly ReadVirtualCache _versionCache;
private IntPtr _sosLibrary = IntPtr.Zero;
/// <summary>
/// <summary>
/// Create an instance of the hosting class
/// </summary>
- /// <param name="serviceProvider">Service provider</param>
- public SOSHost(IServiceProvider serviceProvider)
+ /// <param name="target">target instance</param>
+ public SOSHost(ITarget target)
{
- DataReader = serviceProvider.GetService<IDataReader>();
- _console = serviceProvider.GetService<IConsoleService>();
- AnalyzeContext = serviceProvider.GetService<AnalyzeContext>();
- _memoryService = serviceProvider.GetService<MemoryService>();
- _threadService = serviceProvider.GetService<IThreadService>();
- _versionCache = new ReadVirtualCache(_memoryService);
+ Target = target;
+ ConsoleService = target.Services.GetService<IConsoleService>();
+ ModuleService = target.Services.GetService<IModuleService>();
+ ThreadService = target.Services.GetService<IThreadService>();
+ MemoryService = target.Services.GetService<IMemoryService>();
+ _ignoreAddressBitsMask = MemoryService.SignExtensionMask();
string rid = InstallHelper.GetRid();
SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var debugClient = new DebugClient(this);
- _ccw = debugClient;
_interface = debugClient.IDebugClient;
}
else
{
var lldbServices = new LLDBServices(this);
- _ccw = lldbServices;
_interface = lldbServices.ILLDBServices;
}
}
/// <summary>
/// Loads and initializes the SOS module.
/// </summary>
- /// <param name="tempDirectory">Temporary directory created to download DAC module</param>
- /// <param name="isDesktop">if true, desktop runtime, else .NET Core runtime</param>
- /// <param name="dacFilePath">The path to DAC that CLRMD loaded or downloaded or null</param>
- /// <param name="dbiFilePath">The path to DBI (for future use) or null</param>
- public void InitializeSOSHost(string tempDirectory, bool isDesktop, string dacFilePath, string dbiFilePath)
+ public void InitializeSOSHost()
{
if (_sosLibrary == IntPtr.Zero)
{
string sosPath = Path.Combine(SOSPath, sos);
try
{
- _sosLibrary = DataTarget.PlatformFunctions.LoadLibrary(sosPath);
+ _sosLibrary = Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.LoadLibrary(sosPath);
}
catch (DllNotFoundException ex)
{
{
throw new FileNotFoundException($"SOS module {sosPath} not found");
}
- IntPtr initializeAddress = DataTarget.PlatformFunctions.GetLibraryExport(_sosLibrary, SOSInitialize);
- if (initializeAddress == IntPtr.Zero)
+ var initializeFunc = GetDelegateFunction<SOSInitializeDelegate>(_sosLibrary, SOSInitialize);
+ if (initializeFunc == null)
{
throw new EntryPointNotFoundException($"Can not find SOS module initialization function: {SOSInitialize}");
}
- var initializeFunc = (SOSInitializeDelegate)Marshal.GetDelegateForFunctionPointer(initializeAddress, typeof(SOSInitializeDelegate));
-
+
// SOS depends on that the temp directory ends with "/".
+ string tempDirectory = Target.GetTempDirectory();
if (!string.IsNullOrEmpty(tempDirectory) && tempDirectory[tempDirectory.Length - 1] != Path.DirectorySeparatorChar)
{
tempDirectory += Path.DirectorySeparatorChar;
}
+ var runtimeService = Target.Services.GetService<IRuntimeService>();
int result = initializeFunc(
ref SymbolReader.SymbolCallbacks,
Marshal.SizeOf<SymbolReader.SOSNetCoreCallbacks>(),
tempDirectory,
- AnalyzeContext.RuntimeModuleDirectory,
- isDesktop,
- dacFilePath,
- dbiFilePath,
+ runtimeService.RuntimeModuleDirectory,
+ false,
+ null,
+ null,
SymbolReader.IsSymbolStoreEnabled());
if (result != 0)
{
throw new InvalidOperationException($"SOS initialization FAILED 0x{result:X8}");
}
- Trace.TraceInformation("SOS initialized: tempDirectory '{0}' dacFilePath '{1}' sosPath '{2}'", tempDirectory, dacFilePath, sosPath);
+ Trace.TraceInformation("SOS initialized: tempDirectory '{0}' sosPath '{1}'", tempDirectory, sosPath);
}
}
{
Debug.Assert(_sosLibrary != null);
- IntPtr commandAddress = DataTarget.PlatformFunctions.GetLibraryExport(_sosLibrary, command);
- if (commandAddress == IntPtr.Zero)
+ var commandFunc = GetDelegateFunction<SOSCommandDelegate>(_sosLibrary, command);
+ if (commandFunc == null)
{
throw new EntryPointNotFoundException($"Can not find SOS command: {command}");
}
- var commandFunc = (SOSCommandDelegate)Marshal.GetDelegateForFunctionPointer(commandAddress, typeof(SOSCommandDelegate));
-
int result = commandFunc(_interface, arguments ?? "");
- if (result != 0)
+ if (result != HResult.S_OK)
{
Trace.TraceError($"SOS command FAILED 0x{result:X8}");
}
internal int GetInterrupt(
IntPtr self)
{
- return AnalyzeContext.CancellationToken.IsCancellationRequested ? HResult.S_OK : HResult.E_FAIL;
+ return ConsoleService.CancellationToken.IsCancellationRequested ? HResult.S_OK : HResult.E_FAIL;
}
internal int OutputVaList(
try
{
// The text has already been formated by sos
- _console.Write(format);
+ ConsoleService.Write(format);
}
catch (OperationCanceledException)
{
IntPtr self,
IMAGE_FILE_MACHINE* type)
{
- switch (DataReader.Architecture)
+ switch (Target.Architecture)
{
- case Microsoft.Diagnostics.Runtime.Architecture.Amd64:
+ case Architecture.X64:
*type = IMAGE_FILE_MACHINE.AMD64;
break;
- case Microsoft.Diagnostics.Runtime.Architecture.X86:
+ case Architecture.X86:
*type = IMAGE_FILE_MACHINE.I386;
break;
- case Microsoft.Diagnostics.Runtime.Architecture.Arm:
+ case Architecture.Arm:
*type = IMAGE_FILE_MACHINE.THUMB2;
break;
- case Microsoft.Diagnostics.Runtime.Architecture.Arm64:
+ case Architecture.Arm64:
*type = IMAGE_FILE_MACHINE.ARM64;
break;
default:
uint bytesRequested,
uint* pbytesRead)
{
- if (_memoryService.ReadMemory(address, buffer, unchecked((int)bytesRequested), out int bytesRead))
+ address &= _ignoreAddressBitsMask;
+ if (MemoryService.ReadMemory(address, buffer, unchecked((int)bytesRequested), out int bytesRead))
{
Write(pbytesRead, (uint)bytesRead);
return HResult.S_OK;
out uint loaded,
out uint unloaded)
{
- loaded = (uint)DataReader.EnumerateModules().Count();
+ loaded = (uint)ModuleService.EnumerateModules().Count();
unloaded = 0;
return HResult.S_OK;
}
baseAddress = 0;
try
{
- ModuleInfo module = DataReader.EnumerateModules().ElementAt((int)index);
- if (module == null)
- {
- return HResult.E_FAIL;
- }
- baseAddress = module.ImageBase;
+ baseAddress = ModuleService.GetModuleFromIndex((int)index).ImageBase;
}
- catch (ArgumentOutOfRangeException)
+ catch (DiagnosticsException)
{
return HResult.E_FAIL;
}
uint* index,
ulong* baseAddress)
{
- Debug.Assert(startIndex == 0);
Write(index);
Write(baseAddress);
- uint i = 0;
- foreach (ModuleInfo module in DataReader.EnumerateModules())
+ if (startIndex != 0)
{
- if (IsModuleEqual(module, name))
- {
- Write(index, i);
- Write(baseAddress, module.ImageBase);
- return HResult.S_OK;
- }
- i++;
+ return HResult.E_INVALIDARG;
+ }
+ if (Target.OperatingSystem == OSPlatform.Windows)
+ {
+ name = Path.GetFileNameWithoutExtension(name) + ".dll";
+ }
+ IModule module = ModuleService.GetModuleFromModuleName(name).FirstOrDefault();
+ if (module != null)
+ {
+ Write(index, (uint)module.ModuleIndex);
+ Write(baseAddress, module.ImageBase);
+ return HResult.S_OK;
}
return HResult.E_FAIL;
}
uint* index,
ulong* baseAddress)
{
- Debug.Assert(startIndex == 0);
+ Write(index);
+ Write(baseAddress);
+ if (startIndex != 0)
+ {
+ return HResult.E_INVALIDARG;
+ }
// This causes way too many problems on Linux because of various
// bugs in the CLRMD ELF dump reader module enumeration and isn't
// necessary on linux anyway.
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (Target.OperatingSystem == OSPlatform.Windows)
{
- // Find the module that contains the offset.
- uint i = 0;
- foreach (ModuleInfo module in DataReader.EnumerateModules())
+ IModule module = ModuleService.GetModuleFromAddress(offset);
+ if (module != null)
{
- if (offset >= module.ImageBase && offset < (module.ImageBase + (ulong)module.IndexFileSize))
- {
- Write(index, i);
- Write(baseAddress, module.ImageBase);
- return HResult.S_OK;
- }
- i++;
+ Write(index, (uint)module.ModuleIndex);
+ Write(baseAddress, module.ImageBase);
+ return HResult.S_OK;
}
}
return HResult.E_FAIL;
Write(imageNameSize);
Write(moduleNameSize);
Write(loadedImageNameSize);
-
- uint i = 0;
- foreach (ModuleInfo module in DataReader.EnumerateModules())
- {
- if ((index != uint.MaxValue && i == index) || (index == uint.MaxValue && baseAddress == module.ImageBase))
+
+ IModule module;
+ try
+ {
+ if (index != uint.MaxValue)
{
- imageNameBuffer?.Append(module.FileName);
- Write(imageNameSize, (uint)module.FileName.Length + 1);
-
- string moduleName = GetModuleName(module);
- moduleNameBuffer?.Append(moduleName);
- Write(moduleNameSize, (uint)moduleName.Length + 1);
- return HResult.S_OK;
+ module = ModuleService.GetModuleFromIndex(unchecked((int)index));
+ }
+ else
+ {
+ module = ModuleService.GetModuleFromBaseAddress(baseAddress);
}
- i++;
}
- return HResult.E_FAIL;
+ catch (DiagnosticsException)
+ {
+ return HResult.E_FAIL;
+ }
+ imageNameBuffer?.Append(module.FileName);
+ Write(imageNameSize, (uint)module.FileName.Length + 1);
+
+ string moduleName = GetFileName(module.FileName);
+ moduleNameBuffer?.Append(moduleName);
+ Write(moduleNameSize, (uint)moduleName.Length + 1);
+ return HResult.S_OK;
}
internal unsafe int GetModuleParameters(
uint start,
DEBUG_MODULE_PARAMETERS* moduleParams)
{
- Debug.Assert(bases != null);
- Debug.Assert(start == 0);
-
- foreach (ModuleInfo module in DataReader.EnumerateModules())
+ if (start != 0 || bases == null)
+ {
+ return HResult.E_INVALIDARG;
+ }
+ foreach (IModule module in ModuleService.EnumerateModules())
{
for (int i = 0; i < count; i++)
{
if (bases[i] == module.ImageBase)
{
moduleParams[i].Base = module.ImageBase;
- moduleParams[i].Size = (uint)module.IndexFileSize;
+ moduleParams[i].Size = (uint)module.ImageSize;
moduleParams[i].TimeDateStamp = (uint)module.IndexTimeStamp;
moduleParams[i].Checksum = 0;
moduleParams[i].Flags = DEBUG_MODULE.LOADED;
uint imageNameSize = (uint)module.FileName.Length + 1;
moduleParams[i].ImageNameSize = imageNameSize;
- string moduleName = GetModuleName(module);
+ string moduleName = GetFileName(module.FileName);
uint moduleNameSize = (uint)moduleName.Length + 1;
moduleParams[i].ModuleNameSize = moduleNameSize;
uint bufferSize,
uint* verInfoSize)
{
- Debug.Assert(buffer != null);
- Debug.Assert(verInfoSize == null);
-
- uint i = 0;
- foreach (ModuleInfo module in DataReader.EnumerateModules())
+ if (item == null || buffer == null || bufferSize == 0)
{
- if ((index != uint.MaxValue && i == index) || (index == uint.MaxValue && baseAddress == module.ImageBase))
+ return HResult.E_INVALIDARG;
+ }
+ IModule module;
+ try
+ {
+ if (index != uint.MaxValue)
{
- if (item == "\\")
- {
- uint versionSize = (uint)Marshal.SizeOf(typeof(VS_FIXEDFILEINFO));
- Write(verInfoSize, versionSize);
- if (bufferSize < versionSize)
- {
- return HResult.E_INVALIDARG;
- }
- VS_FIXEDFILEINFO* fileInfo = (VS_FIXEDFILEINFO*)buffer;
- fileInfo->dwSignature = 0;
- fileInfo->dwStrucVersion = 0;
- fileInfo->dwFileFlagsMask = 0;
- fileInfo->dwFileFlags = 0;
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- VersionInfo versionInfo = module.Version;
- fileInfo->dwFileVersionMS = (uint)versionInfo.Minor | (uint)versionInfo.Major << 16;
- fileInfo->dwFileVersionLS = (uint)versionInfo.Patch | (uint)versionInfo.Revision << 16;
- return HResult.S_OK;
- }
- else
- {
- if (SearchVersionString(module, out string versionString))
- {
- int spaceIndex = versionString.IndexOf(' ');
- if (spaceIndex > 0)
- {
- if (versionString[spaceIndex - 1] == '.')
- {
- spaceIndex--;
- }
- string versionToParse = versionString.Substring(0, spaceIndex);
- try
- {
- Version versionInfo = Version.Parse(versionToParse);
- fileInfo->dwFileVersionMS = (uint)versionInfo.Minor | (uint)versionInfo.Major << 16;
- fileInfo->dwFileVersionLS = (uint)versionInfo.Revision | (uint)versionInfo.Build << 16;
- return HResult.S_OK;
- }
- catch (ArgumentException ex)
- {
- Trace.TraceError($"GetModuleVersion FAILURE: '{versionToParse}' '{versionString}' {ex.ToString()}");
- }
- }
- }
- break;
- }
- }
- else if (item == "\\StringFileInfo\\040904B0\\FileVersion")
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return HResult.E_INVALIDARG;
- }
- else
- {
- if (SearchVersionString(module, out string versionString))
- {
- byte[] source = Encoding.ASCII.GetBytes(versionString + '\0');
- Marshal.Copy(source, 0, new IntPtr(buffer), Math.Min(source.Length, (int)bufferSize));
- return HResult.S_OK;
- }
- break;
- }
- }
- else
- {
- return HResult.E_INVALIDARG;
- }
+ module = ModuleService.GetModuleFromIndex(unchecked((int)index));
+ }
+ else
+ {
+ module = ModuleService.GetModuleFromBaseAddress(baseAddress);
}
- i++;
}
+ catch (DiagnosticsException)
+ {
+ return HResult.E_FAIL;
+ }
+ if (item == "\\")
+ {
+ int versionSize = Marshal.SizeOf(typeof(VS_FIXEDFILEINFO));
+ Write(verInfoSize, (uint)versionSize);
+ if (bufferSize < versionSize)
+ {
+ return HResult.E_INVALIDARG;
+ }
+ if (!module.Version.HasValue)
+ {
+ return HResult.E_FAIL;
+ }
+ VS_FIXEDFILEINFO* fileInfo = (VS_FIXEDFILEINFO*)buffer;
+ fileInfo->dwSignature = 0;
+ fileInfo->dwStrucVersion = 0;
+ fileInfo->dwFileFlagsMask = 0;
+ fileInfo->dwFileFlags = 0;
- return HResult.E_FAIL;
- }
-
- private static readonly byte[] s_versionString = Encoding.ASCII.GetBytes("@(#)Version ");
- private static readonly int s_versionLength = s_versionString.Length;
-
- private bool SearchVersionString(ModuleInfo moduleInfo, out string fileVersion)
- {
- byte[] buffer = new byte[s_versionString.Length];
- ulong address = moduleInfo.ImageBase;
- int size = moduleInfo.IndexFileSize;
-
- _versionCache.Clear();
-
- while (size > 0) {
- bool result = _versionCache.Read(address, buffer, s_versionString.Length, out int cbBytesRead);
- if (result && cbBytesRead >= s_versionLength)
+ VersionInfo versionInfo = module.Version.Value;
+ fileInfo->dwFileVersionMS = (uint)versionInfo.Minor | (uint)versionInfo.Major << 16;
+ fileInfo->dwFileVersionLS = (uint)versionInfo.Patch | (uint)versionInfo.Revision << 16;
+ }
+ else if (item == "\\StringFileInfo\\040904B0\\FileVersion")
+ {
+ *buffer = 0;
+ string versionString = module.VersionString;
+ if (versionString == null)
{
- if (s_versionString.SequenceEqual(buffer))
- {
- address += (ulong)s_versionLength;
- size -= s_versionLength;
-
- var sb = new StringBuilder();
- byte[] ch = new byte[1];
- while (true)
- {
- // Now read the version string a char/byte at a time
- result = _versionCache.Read(address, ch, ch.Length, out cbBytesRead);
-
- // Return not found if there are any failures or problems while reading the version string.
- if (!result || cbBytesRead < ch.Length || size <= 0) {
- break;
- }
-
- // Found the end of the string
- if (ch[0] == '\0') {
- fileVersion = sb.ToString();
- return true;
- }
- sb.Append(Encoding.ASCII.GetChars(ch));
- address++;
- size--;
- }
- // Return not found if overflowed the fileVersionBuffer (not finding a null).
- break;
- }
- address++;
- size--;
+ return HResult.E_FAIL;
}
- else
+ try
{
- address += (ulong)s_versionLength;
- size -= s_versionLength;
+ byte[] source = Encoding.ASCII.GetBytes(versionString + '\0');
+ Marshal.Copy(source, 0, new IntPtr(buffer), Math.Min(source.Length, (int)bufferSize));
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ return HResult.E_INVALIDARG;
}
}
-
- fileVersion = null;
- return false;
+ else
+ {
+ return HResult.E_INVALIDARG;
+ }
+ return HResult.S_OK;
}
internal unsafe int GetLineByOffset(
IntPtr context,
uint contextSize)
{
- if (!AnalyzeContext.CurrentThreadId.HasValue)
+ if (!ThreadService.CurrentThreadId.HasValue)
{
return HResult.E_FAIL;
}
- return GetThreadContextById(self, AnalyzeContext.CurrentThreadId.Value, 0, contextSize, context);
+ return GetThreadContextBySystemId(self, ThreadService.CurrentThreadId.Value, 0, contextSize, context);
}
- internal int GetThreadContextById(
+ internal int GetThreadContextBySystemId(
IntPtr self,
uint threadId,
uint contextFlags,
byte[] registerContext;
try
{
- registerContext = _threadService.GetThreadContext(threadId);
+ registerContext = ThreadService.GetThreadInfoFromId(threadId).GetThreadContext();
}
catch (DiagnosticsException)
{
IntPtr self,
out uint number)
{
- number = (uint)_threadService.EnumerateThreads().Count();
+ number = (uint)ThreadService.EnumerateThreads().Count();
return HResult.S_OK;
}
out uint total,
out uint largestProcess)
{
- total = (uint)_threadService.EnumerateThreads().Count();
+ total = (uint)ThreadService.EnumerateThreads().Count();
largestProcess = total;
return HResult.S_OK;
}
- internal int GetCurrentProcessId(
+ internal int GetCurrentProcessSystemId(
IntPtr self,
out uint id)
{
- id = (uint)DataReader.ProcessId;
+ if (!Target.ProcessId.HasValue) {
+ id = 0;
+ return HResult.E_FAIL;
+ }
+ id = Target.ProcessId.Value;
return HResult.S_OK;
}
IntPtr self,
out uint id)
{
- if (!AnalyzeContext.CurrentThreadId.HasValue)
- {
+ if (!ThreadService.CurrentThreadId.HasValue) {
id = 0;
return HResult.E_FAIL;
}
- return GetThreadIdBySystemId(self, AnalyzeContext.CurrentThreadId.Value, out id);
+ return GetThreadIdBySystemId(self, ThreadService.CurrentThreadId.Value, out id);
}
internal int SetCurrentThreadId(
{
try
{
- ThreadInfo threadInfo = _threadService.GetThreadInfoFromIndex(unchecked((int)id));
- AnalyzeContext.CurrentThreadId = threadInfo.ThreadId;
+ ThreadService.CurrentThreadId = ThreadService.GetThreadInfoFromIndex(unchecked((int)id)).ThreadId;
}
catch (DiagnosticsException)
{
IntPtr self,
out uint sysId)
{
- if (!AnalyzeContext.CurrentThreadId.HasValue)
+ uint? id = ThreadService.CurrentThreadId;
+ if (id.HasValue)
{
- sysId = 0;
- return HResult.E_FAIL;
+ sysId = id.Value;
+ return HResult.S_OK;
}
- sysId = AnalyzeContext.CurrentThreadId.Value;
- return HResult.S_OK;
+ sysId = 0;
+ return HResult.E_FAIL;
}
internal unsafe int GetThreadIdsByIndex(
uint* ids,
uint* sysIds)
{
- int number = _threadService.EnumerateThreads().Count();
+ int number = ThreadService.EnumerateThreads().Count();
if (start >= number || start + count > number)
{
return HResult.E_INVALIDARG;
}
int index = 0;
- foreach (ThreadInfo threadInfo in _threadService.EnumerateThreads())
+ foreach (IThread threadInfo in ThreadService.EnumerateThreads())
{
if (index >= start && index < start + count)
{
{
try
{
- ThreadInfo threadInfo = _threadService.GetThreadInfoFromId(sysId);
+ IThread threadInfo = ThreadService.GetThreadInfoFromId(sysId);
id = (uint)threadInfo.ThreadIndex;
return HResult.S_OK;
}
IntPtr self,
ulong* offset)
{
- if (AnalyzeContext.CurrentThreadId.HasValue)
+ if (ThreadService.CurrentThreadId.HasValue)
{
- uint threadId = AnalyzeContext.CurrentThreadId.Value;
+ uint threadId = ThreadService.CurrentThreadId.Value;
try
{
- ulong teb = _threadService.GetThreadInfoFromId(threadId).ThreadTeb;
+ ulong teb = ThreadService.GetThreadInfoFromId(threadId).GetThreadTeb();
Write(offset, teb);
return HResult.S_OK;
}
IntPtr self,
out ulong offset)
{
- return GetRegister(_threadService.InstructionPointerIndex, out offset);
+ return GetRegister(ThreadService.InstructionPointerIndex, out offset);
}
internal int GetStackOffset(
IntPtr self,
out ulong offset)
{
- return GetRegister(_threadService.StackPointerIndex, out offset);
+ return GetRegister(ThreadService.StackPointerIndex, out offset);
}
internal int GetFrameOffset(
IntPtr self,
out ulong offset)
{
- return GetRegister(_threadService.FramePointerIndex, out offset);
+ return GetRegister(ThreadService.FramePointerIndex, out offset);
}
internal int GetIndexByName(
string name,
out uint index)
{
- if (!_threadService.GetRegisterIndexByName(name, out int value)) {
+ if (!ThreadService.GetRegisterIndexByName(name, out int value)) {
index = 0;
return HResult.E_INVALIDARG;
}
// SOS expects the DEBUG_VALUE field to be set based on the
// processor architecture instead of the register size.
- switch (DataReader.PointerSize)
+ switch (MemoryService.PointerSize)
{
case 8:
value = new DEBUG_VALUE {
string register,
out ulong value)
{
- if (!_threadService.GetRegisterIndexByName(register, out int index)) {
+ if (!ThreadService.GetRegisterIndexByName(register, out int index)) {
value = 0;
return HResult.E_INVALIDARG;
}
int index,
out ulong value)
{
- if (AnalyzeContext.CurrentThreadId.HasValue)
+ if (ThreadService.CurrentThreadId.HasValue)
{
- uint threadId = AnalyzeContext.CurrentThreadId.Value;
- if (_threadService.GetRegisterValue(threadId, index, out value)) {
- return HResult.S_OK;
+ IThread thread = ThreadService.GetThreadInfoFromId(ThreadService.CurrentThreadId.Value);
+ if (thread != null)
+ {
+ if (thread.GetRegisterValue(index, out value))
+ {
+ return HResult.S_OK;
+ }
}
}
value = 0;
return HResult.E_FAIL;
}
- internal static bool IsCoreClrRuntimeModule(ModuleInfo module)
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return IsModuleEqual(module, "coreclr") || IsModuleEqual(module, "libcoreclr");
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return IsModuleEqual(module, "libcoreclr.so");
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return IsModuleEqual(module, "libcoreclr.dylib");
- }
- return false;
- }
-
- internal static bool IsDesktopRuntimeModule(ModuleInfo module)
- {
- return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && IsModuleEqual(module, DesktopRuntimeModuleName);
- }
+ #endregion
- internal static bool IsModuleEqual(ModuleInfo module, string moduleName)
+ /// <summary>
+ /// Helper function to get pinvoke entries into native modules
+ /// </summary>
+ /// <typeparam name="T">function delegate</typeparam>
+ /// <param name="library">module name</param>
+ /// <param name="functionName">name of function</param>
+ /// <returns>delegate instance or null</returns>
+ public static T GetDelegateFunction<T>(IntPtr library, string functionName)
+ where T : Delegate
{
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
- return StringComparer.OrdinalIgnoreCase.Equals(GetModuleName(module), moduleName);
- }
- else {
- return string.Equals(GetModuleName(module), moduleName);
+ IntPtr functionAddress = Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.GetLibraryExport(library, functionName);
+ if (functionAddress == IntPtr.Zero) {
+ return default;
}
+ return (T)Marshal.GetDelegateForFunctionPointer(functionAddress, typeof(T));
}
- internal static string GetModuleName(ModuleInfo module)
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
- return Path.GetFileNameWithoutExtension(module.FileName);
- }
- else {
- return Path.GetFileName(module.FileName);
- }
- }
+ private string GetFileName(string fileName) => Target.OperatingSystem == OSPlatform.Windows ? Path.GetFileNameWithoutExtension(fileName) : Path.GetFileName(fileName);
internal unsafe static void Write(uint* pointer, uint value = 0)
{
*pointer = value;
}
}
-
- #endregion
- }
-
- class ReadVirtualCache
- {
- private const int CACHE_SIZE = 4096;
-
- private readonly MemoryService _memoryService;
- private readonly byte[] _cache = new byte[CACHE_SIZE];
- private ulong _startCache;
- private bool _cacheValid;
- private int _cacheSize;
-
- internal ReadVirtualCache(MemoryService memoryService)
- {
- _memoryService = memoryService;
- Clear();
- }
-
- internal bool Read(ulong address, byte[] buffer, int bufferSize, out int bytesRead)
- {
- bytesRead = 0;
-
- if (bufferSize == 0) {
- return true;
- }
-
- if (bufferSize > CACHE_SIZE)
- {
- // Don't even try with the cache
- return _memoryService.ReadMemory(address, buffer, bufferSize, out bytesRead);
- }
-
- if (!_cacheValid || (address < _startCache) || (address > (_startCache + (ulong)(_cacheSize - bufferSize))))
- {
- _cacheValid = false;
- _startCache = address;
- if (!_memoryService.ReadMemory(_startCache, _cache, _cache.Length, out int cbBytesRead)) {
- return false;
- }
- _cacheSize = cbBytesRead;
- _cacheValid = true;
- }
-
- int size = Math.Min(bufferSize, _cacheSize);
- Array.Copy(_cache, (int)(address - _startCache), buffer, 0, size);
- bytesRead = size;
- return true;
- }
-
- internal void Clear()
- {
- _cacheValid = false;
- _cacheSize = CACHE_SIZE;
- }
}
}
using System;
using System.Runtime.InteropServices;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugAdvanced
{
using System.Runtime.InteropServices;
using System.Text;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugClient : COMCallableIUnknown
{
internal readonly IntPtr IDebugClient;
- readonly DebugAdvanced _debugAdvanced;
- readonly DebugControl _debugControl;
- readonly DebugDataSpaces _debugDataSpaces;
- readonly DebugRegisters _debugRegisters;
- readonly DebugSymbols _debugSymbols;
- readonly DebugSystemObjects _debugSystemObjects;
+ private readonly DebugAdvanced _debugAdvanced;
+ private readonly DebugControl _debugControl;
+ private readonly DebugDataSpaces _debugDataSpaces;
+ private readonly DebugRegisters _debugRegisters;
+ private readonly DebugSymbols _debugSymbols;
+ private readonly DebugSystemObjects _debugSystemObjects;
/// <summary>
/// Create an instance of the service wrapper SOS uses.
using System.Runtime.InteropServices;
using System.Text;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugControl
{
using System;
using System.Runtime.InteropServices;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugDataSpaces
{
using System.Runtime.InteropServices;
using System.Text;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugRegisters
{
using System.Runtime.InteropServices;
using System.Text;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugSymbols
{
using System.Runtime.InteropServices;
using System.Text;
-namespace SOS
+namespace SOS.Hosting
{
internal unsafe class DebugSystemObjects
{
builder.AddMethod(new GetEventProcessDelegate((self, id) => DebugClient.NotImplemented));
builder.AddMethod(new GetCurrentThreadIdDelegate(soshost.GetCurrentThreadId));
builder.AddMethod(new SetCurrentThreadIdDelegate(soshost.SetCurrentThreadId));
- builder.AddMethod(new GetCurrentProcessIdDelegate(soshost.GetCurrentProcessId));
+ builder.AddMethod(new GetCurrentProcessIdDelegate((self, id) => DebugClient.NotImplemented));
builder.AddMethod(new SetCurrentProcessIdDelegate((self, id) => DebugClient.NotImplemented));
builder.AddMethod(new GetNumberThreadsDelegate(soshost.GetNumberThreads));
builder.AddMethod(new GetTotalNumberThreadsDelegate(soshost.GetTotalNumberThreads));
builder.AddMethod(new GetProcessIdByDataOffsetDelegate((self, offset, id) => DebugClient.NotImplemented));
builder.AddMethod(new GetCurrentProcessPebDelegate((self, offset) => DebugClient.NotImplemented));
builder.AddMethod(new GetProcessIdByPebDelegate((self, offset, id) => DebugClient.NotImplemented));
- builder.AddMethod(new GetCurrentProcessSystemIdDelegate((self, sysId) => DebugClient.NotImplemented));
+ builder.AddMethod(new GetCurrentProcessSystemIdDelegate(soshost.GetCurrentProcessSystemId));
builder.AddMethod(new GetProcessIdBySystemIdDelegate((self, sysId, id) => DebugClient.NotImplemented));
builder.AddMethod(new GetCurrentProcessHandleDelegate((self, handle) => DebugClient.NotImplemented));
builder.AddMethod(new GetProcessIdByHandleDelegate((self, handle, id) => DebugClient.NotImplemented));
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int GetCurrentProcessIdDelegate(
IntPtr self,
- [Out] out uint Id);
+ [Out] uint* Id);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int SetCurrentProcessIdDelegate(
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int GetCurrentProcessSystemIdDelegate(
IntPtr self,
- [Out] uint* SysId);
+ [Out] out uint SysId);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int GetProcessIdBySystemIdDelegate(
/// </summary>
/// <param name="key">index of the file to retrieve</param>
/// <returns>stream or null</returns>
- internal static SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key)
+ public static SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key)
{
try
{
ENDIF:MAJOR_RUNTIME_VERSION_1
IFDEF:MAJOR_RUNTIME_VERSION_2
VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+System\.IO.TextWriter\s+<DECVAL>\s+shared\s+static\s+Null\s+
-VERIFY:\s+>>\s+Domain:Value\s+<HEXVAL>:<HEXVAL>\s+<<\s+
+VERIFY:\s+>>\s+Domain:Value\s+<HEXVAL>:(<HEXVAL>|NotInit)\s+<<\s+
ENDIF:MAJOR_RUNTIME_VERSION_2
IFDEF:MAJOR_RUNTIME_VERSION_GE_3
VERIFY:\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+System\.IO.TextWriter\s+<DECVAL>\s+static\s+<HEXVAL>\s+Null\s+
SOSCOMMAND:SetSymbolServer -ms
!IFDEF:DOTNETDUMP
+IFDEF:WINDOWS
SOSCOMMAND:SetHostRuntime
+ENDIF:WINDOWS
+!IFDEF:WINDOWS
+COMMAND:sethostruntime
+ENDIF:WINDOWS
ENDIF:DOTNETDUMP
+SOSCOMMAND:SOSStatus
+
# Verify that ClrStack with no options works
-SOSCOMMAND:SetSymbolServer -ms
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:SetHostRuntime
-ENDIF:DOTNETDUMP
SOSCOMMAND:ClrStack
VERIFY:.*OS Thread Id:\s+0x<HEXVAL>\s+.*
VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+
VERIFY:.*\s+<HEXVAL>\s+<HEXVAL>\s+NestedExceptionTest\.Program\.Main\(.*\)\s+\[(?i:.*[\\|/]NestedExceptionTest\.cs) @ 13\s*\]\s+
ENDIF:64BIT
-# 2) Verifying that ClrStack with managed/native mixed works
SOSCOMMAND:SetSymbolServer -ms -loadsymbols
!IFDEF:DOTNETDUMP
+IFDEF:WINDOWS
SOSCOMMAND:SetHostRuntime
+ENDIF:WINDOWS
+!IFDEF:WINDOWS
+COMMAND:sethostruntime
+ENDIF:WINDOWS
ENDIF:DOTNETDUMP
+
+# 2) Verifying that ClrStack with managed/native mixed works
SOSCOMMAND:ClrStack -f
VERIFY:.*OS Thread Id:\s+0x<HEXVAL>\s+.*
VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+
SOSCOMMAND:SetSymbolServer -ms
!IFDEF:DOTNETDUMP
+IFDEF:WINDOWS
SOSCOMMAND:SetHostRuntime
+ENDIF:WINDOWS
+!IFDEF:WINDOWS
+COMMAND:sethostruntime
+ENDIF:WINDOWS
ENDIF:DOTNETDUMP
IFDEF:DOTNETDUMP
// 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.NETCore.Client;
using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.DebugServices.Implementation;
+using Microsoft.Diagnostics.ExtensionCommands;
using Microsoft.Diagnostics.Repl;
using Microsoft.Diagnostics.Runtime;
-using Microsoft.SymbolStore;
-using Microsoft.SymbolStore.KeyGenerators;
-using SOS;
+using SOS.Hosting;
using System;
-using System.Collections.Generic;
-using System.CommandLine;
-using System.Diagnostics;
using System.IO;
-using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Diagnostic.Tools.Dump.ExtensionCommands;
-using Microsoft.Diagnostics.Runtime.DataReaders.Implementation;
-using System.CommandLine.Help;
namespace Microsoft.Diagnostics.Tools.Dump
{
- public class Analyzer
+ public class Analyzer : IHost
{
private readonly ServiceProvider _serviceProvider;
private readonly ConsoleProvider _consoleProvider;
private readonly CommandProcessor _commandProcessor;
- private bool _isDesktop;
- private string _dacFilePath;
+ private readonly SymbolService _symbolService;
+ private Target _target;
/// <summary>
/// Enable the assembly resolver to get the right SOS.NETCore version (the one
{
_serviceProvider = new ServiceProvider();
_consoleProvider = new ConsoleProvider();
- _commandProcessor = new CommandProcessor(_serviceProvider, _consoleProvider, new Assembly[] { typeof(Analyzer).Assembly });
+ _commandProcessor = new CommandProcessor();
+ _symbolService = new SymbolService(this);
+
+ _serviceProvider.AddService<IHost>(this);
+ _serviceProvider.AddService<IConsoleService>(_consoleProvider);
+ _serviceProvider.AddService<ICommandService>(_commandProcessor);
+ _serviceProvider.AddService<ISymbolService>(_symbolService);
+
+ _commandProcessor.AddCommands(new Assembly[] { typeof(Analyzer).Assembly });
+ _commandProcessor.AddCommands(typeof(HelpCommand), (services) => new HelpCommand(_commandProcessor, services));
+ _commandProcessor.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleProvider.Stop));
}
- public async Task<int> Analyze(FileInfo dump_path, string[] command)
+ public Task<int> Analyze(FileInfo dump_path, string[] command)
{
_consoleProvider.WriteLine($"Loading core dump: {dump_path} ...");
try
- {
- using (DataTarget target = DataTarget.LoadDump(dump_path.FullName))
- {
- _consoleProvider.WriteLine("Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.");
- _consoleProvider.WriteLine("Type 'quit' or 'exit' to exit the session.");
-
- // Add all the services needed by commands and other services
- AddServices(target);
+ {
+ using DataTarget dataTarget = DataTarget.LoadDump(dump_path.FullName);
- // Set the default symbol cache to match Visual Studio's when running on Windows
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
- SymbolReader.DefaultSymbolCache = Path.Combine(Path.GetTempPath(), "SymbolCache");
- }
+ OSPlatform targetPlatform = dataTarget.DataReader.TargetPlatform;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
+ targetPlatform = OSPlatform.OSX;
+ }
+ _target = new TargetFromDataReader(dataTarget.DataReader, targetPlatform, this, dump_path.FullName);
- // Automatically enable symbol server support
- SymbolReader.InitializeSymbolStore(
- logging: false,
- msdl: true,
- symweb: false,
- tempDirectory: null,
- symbolServerPath: null,
- authToken: null,
- timeoutInMinutes: 0,
- symbolCachePath: null,
- symbolDirectoryPath: null,
- windowsSymbolPath: null);
+ _target.ServiceProvider.AddServiceFactory<SOSHost>(() => {
+ var sosHost = new SOSHost(_target);
+ sosHost.InitializeSOSHost();
+ return sosHost;
+ });
- target.BinaryLocator = new BinaryLocator();
+ // Set the default symbol cache to match Visual Studio's when running on Windows
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
+ _symbolService.DefaultSymbolCache = Path.Combine(Path.GetTempPath(), "SymbolCache");
+ }
+ // Automatically enable symbol server support
+ _symbolService.AddSymbolServer(msdl: true, symweb: false, symbolServerPath: null, authToken: null, timeoutInMinutes: 0);
+ _symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
- // Run the commands from the dotnet-dump command line
- if (command != null)
+ // Run the commands from the dotnet-dump command line
+ if (command != null)
+ {
+ foreach (string cmd in command)
{
- foreach (string cmd in command)
- {
- await _commandProcessor.Parse(cmd);
+ Parse(cmd);
- if (_consoleProvider.Shutdown)
- break;
+ if (_consoleProvider.Shutdown) {
+ break;
}
}
-
+ }
+ if (!_consoleProvider.Shutdown)
+ {
// Start interactive command line processing
- var analyzeContext = _serviceProvider.GetService<AnalyzeContext>();
- await _consoleProvider.Start(async (string commandLine, CancellationToken cancellation) => {
- analyzeContext.CancellationToken = cancellation;
- await _commandProcessor.Parse(commandLine);
+ _consoleProvider.WriteLine("Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.");
+ _consoleProvider.WriteLine("Type 'quit' or 'exit' to exit the session.");
+
+ _consoleProvider.Start((string commandLine, CancellationToken cancellation) => {
+ Parse(commandLine);
});
}
}
- catch (Exception ex) when
+ catch (Exception ex) when
(ex is ClrDiagnosticsException ||
- ex is FileNotFoundException ||
- ex is DirectoryNotFoundException ||
- ex is UnauthorizedAccessException ||
- ex is PlatformNotSupportedException ||
- ex is UnsupportedCommandException ||
+ ex is FileNotFoundException ||
+ ex is DirectoryNotFoundException ||
+ ex is UnauthorizedAccessException ||
+ ex is PlatformNotSupportedException ||
ex is InvalidDataException ||
ex is InvalidOperationException ||
ex is NotSupportedException)
{
_consoleProvider.WriteLine(OutputType.Error, $"{ex.Message}");
- return 1;
- }
-
- return 0;
- }
-
- /// <summary>
- /// Add all the services needed by commands
- /// </summary>
- private void AddServices(DataTarget target)
- {
- _serviceProvider.AddService(target);
- _serviceProvider.AddService(target.DataReader);
- _serviceProvider.AddService<IConsoleService>(_consoleProvider);
- _serviceProvider.AddService(_commandProcessor);
- _serviceProvider.AddServiceFactory(typeof(IHelpBuilder), _commandProcessor.CreateHelpBuilder);
-
- if (!(target.DataReader is IThreadReader threadReader))
- {
- throw new InvalidOperationException("IThreadReader not implemented");
+ return Task.FromResult(1);
}
-
- // Create common analyze context for commands
- var analyzeContext = new AnalyzeContext() {
- CurrentThreadId = threadReader.EnumerateOSThreadIds().FirstOrDefault()
- };
- _serviceProvider.AddService(analyzeContext);
-
- // Add the thread, memory, SOSHost and ClrRuntime services
- var threadService = new ThreadService(target.DataReader);
- _serviceProvider.AddService<IThreadService>(threadService);
-
- var memoryService = new MemoryService(target.DataReader);
- _serviceProvider.AddService(memoryService);
-
- _serviceProvider.AddServiceFactory(typeof(ClrRuntime), () => CreateRuntime(target));
-
- _serviceProvider.AddServiceFactory(typeof(SOSHost), () => {
- var sosHost = new SOSHost(_serviceProvider);
- sosHost.InitializeSOSHost(SymbolReader.TempDirectory, _isDesktop, _dacFilePath, dbiFilePath: null);
- return sosHost;
- });
-
- // ClrMD helper for extended commands
- _serviceProvider.AddServiceFactory(typeof(ClrMDHelper), () =>
- new ClrMDHelper(_serviceProvider)
- );
- }
-
- /// <summary>
- /// ClrRuntime service factory
- /// </summary>
- private ClrRuntime CreateRuntime(DataTarget target)
- {
- ClrInfo clrInfo = null;
-
- // First check if there is a .NET Core runtime loaded
- foreach (ClrInfo clr in target.ClrVersions)
+ finally
{
- if (clr.Flavor == ClrFlavor.Core)
+ if (_target != null)
{
- clrInfo = clr;
- break;
+ _target.Close();
+ _target = null;
}
+ // Send shutdown event on exit
+ OnShutdownEvent?.Invoke(this, new EventArgs());
}
- // If no .NET Core runtime, then check for desktop runtime
- if (clrInfo == null)
- {
- foreach (ClrInfo clr in target.ClrVersions)
- {
- if (clr.Flavor == ClrFlavor.Desktop)
- {
- clrInfo = clr;
- break;
- }
- }
- }
- if (clrInfo == null) {
- throw new InvalidOperationException("No CLR runtime is present");
- }
- ClrRuntime runtime;
- string dacFilePath = GetDacFile(clrInfo);
- try
- {
- // Ignore the DAC version mismatch that can happen on Linux because the clrmd ELF dump
- // reader returns 0.0.0.0 for the runtime module that the DAC is matched against. This
- // will be fixed in clrmd 2.0 but not 1.1.
- runtime = clrInfo.CreateRuntime(dacFilePath, ignoreMismatch: clrInfo.ModuleInfo.BuildId != null);
- }
- catch (DllNotFoundException ex)
- {
- // This is a workaround for the Microsoft SDK docker images. Can fail when clrmd uses libdl.so
- // to create a symlink to and load the DAC module.
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- throw new DllNotFoundException("Problem initializing CLRMD. Try installing libc6-dev (apt-get install libc6-dev) to work around this problem.", ex);
- }
- else
- {
- throw;
- }
- }
- return runtime;
+ return Task.FromResult(0);
}
- private string GetDacFile(ClrInfo clrInfo)
+ private void Parse(string commandLine)
{
- if (_dacFilePath == null)
+ // If there is no target, then provide just the global services
+ ServiceProvider services = _serviceProvider;
+ if (_target != null)
{
- string dacFileName = GetDacFileName(clrInfo, out OSPlatform platform);
- _dacFilePath = GetLocalDacPath(clrInfo, dacFileName);
- if (_dacFilePath == null)
- {
- if (SymbolReader.IsSymbolStoreEnabled())
- {
- SymbolStoreKey key = null;
-
- if (platform == OSPlatform.OSX)
- {
- unsafe
- {
- var memoryService = _serviceProvider.GetService<MemoryService>();
- SymbolReader.ReadMemoryDelegate readMemory = (ulong address, byte* buffer, int count) => {
- return memoryService.ReadMemory(address, new Span<byte>(buffer, count), out int bytesRead) ? bytesRead : 0;
- };
- KeyGenerator generator = SymbolReader.GetKeyGenerator(
- SymbolReader.RuntimeConfiguration.OSXCore, clrInfo.ModuleInfo.FileName, clrInfo.ModuleInfo.ImageBase, clrInfo.ModuleInfo.IndexFileSize, readMemory);
-
- key = generator.GetKeys(KeyTypeFlags.DacDbiKeys).SingleOrDefault((k) => Path.GetFileName(k.FullPathName) == dacFileName);
- }
- }
- else if (platform == OSPlatform.Linux)
- {
- if (clrInfo.ModuleInfo.BuildId != null)
- {
- IEnumerable<SymbolStoreKey> keys = ELFFileKeyGenerator.GetKeys(
- KeyTypeFlags.DacDbiKeys, clrInfo.ModuleInfo.FileName, clrInfo.ModuleInfo.BuildId.ToArray(), symbolFile: false, symbolFileName: null);
-
- key = keys.SingleOrDefault((k) => Path.GetFileName(k.FullPathName) == dacFileName);
- }
- }
- else if (platform == OSPlatform.Windows)
- {
- // Use the coreclr.dll's id (timestamp/filesize) to download the the dac module.
- key = PEFileKeyGenerator.GetKey(dacFileName, (uint)clrInfo.ModuleInfo.IndexTimeStamp, (uint)clrInfo.ModuleInfo.IndexFileSize);
- }
-
- if (key != null)
- {
- // Now download the DAC module from the symbol server
- _dacFilePath = SymbolReader.GetSymbolFile(key);
- }
+ // Create a per command invocation service provider. These services may change between each command invocation.
+ services = new ServiceProvider(_target.Services);
+
+ // Add the current thread if any
+ services.AddServiceFactory<IThread>(() => {
+ IThreadService threadService = _target.Services.GetService<IThreadService>();
+ if (threadService != null && threadService.CurrentThreadId.HasValue) {
+ return threadService.GetThreadInfoFromId(threadService.CurrentThreadId.Value);
}
+ return null;
+ });
- if (_dacFilePath == null)
- {
- throw new FileNotFoundException($"Could not find matching DAC for this runtime: {clrInfo.ModuleInfo.FileName}");
- }
+ // Add the current runtime and related services
+ var runtimeService = _target.Services.GetService<IRuntimeService>();
+ if (runtimeService != null)
+ {
+ services.AddServiceFactory<IRuntime>(() => runtimeService.CurrentRuntime);
+ services.AddServiceFactory<ClrRuntime>(() => services.GetService<IRuntime>()?.Services.GetService<ClrRuntime>());
+ services.AddServiceFactory<ClrMDHelper>(() => new ClrMDHelper(services.GetService<ClrRuntime>()));
}
- _isDesktop = clrInfo.Flavor == ClrFlavor.Desktop;
}
- return _dacFilePath;
+ _commandProcessor.Execute(commandLine, services);
}
- private static string GetDacFileName(ClrInfo clrInfo, out OSPlatform platform)
- {
- Debug.Assert(!string.IsNullOrEmpty(clrInfo.ModuleInfo.FileName));
- Debug.Assert(!string.IsNullOrEmpty(clrInfo.DacInfo.PlatformSpecificFileName));
+ #region IHost
- platform = OSPlatform.Windows;
+ public event IHost.ShutdownEventHandler OnShutdownEvent;
- switch (Path.GetFileName(clrInfo.ModuleInfo.FileName))
- {
- case "libcoreclr.dylib":
- platform = OSPlatform.OSX;
- return "libmscordaccore.dylib";
+ HostType IHost.HostType => HostType.DotnetDump;
- case "libcoreclr.so":
- platform = OSPlatform.Linux;
+ IServiceProvider IHost.Services => _serviceProvider;
- // If this is the Linux runtime module name, but we are running on Windows return the cross-OS DAC name.
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return "mscordaccore.dll";
- }
- break;
- }
- return clrInfo.DacInfo.PlatformSpecificFileName;
- }
+ ITarget IHost.CurrentTarget => _target;
- private string GetLocalDacPath(ClrInfo clrInfo, string dacFileName)
- {
- string dacFilePath;
- var analyzeContext = _serviceProvider.GetService<AnalyzeContext>();
- if (!string.IsNullOrEmpty(analyzeContext.RuntimeModuleDirectory))
- {
- dacFilePath = Path.Combine(analyzeContext.RuntimeModuleDirectory, dacFileName);
- }
- else
- {
- dacFilePath = Path.Combine(Path.GetDirectoryName(clrInfo.ModuleInfo.FileName), dacFileName);
- }
- if (!File.Exists(dacFilePath))
- {
- dacFilePath = null;
- }
- return dacFilePath;
- }
+ void IHost.SetCurrentTarget(int targetid) => throw new NotImplementedException();
+
+ #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.Repl;
+using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
-using System.CommandLine;
-using System.Threading.Tasks;
+using System;
-namespace Microsoft.Diagnostics.Tools.Dump
+namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "clrmodules", Help = "Lists the managed modules in the process.")]
public class ClrModulesCommand : CommandBase
{
public ClrRuntime Runtime { get; set; }
- public DataTarget DataTarget { get; set; }
+ public IModuleService ModuleService { get; set; }
- [Option(Name = "--verbose", Help = "Displays detailed information about the modules.")]
- [OptionAlias(Name = "-v")]
+ [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the modules.")]
public bool Verbose { get; set; }
public override void Invoke()
{
+ if (Runtime == null)
+ {
+ throw new DiagnosticsException("No CLR runtime set");
+ }
foreach (ClrModule module in Runtime.EnumerateModules())
{
if (Verbose)
WriteLine(" MetadataAddress: {0:X16}", module.MetadataAddress);
WriteLine(" MetadataSize: {0:X16}", module.MetadataLength);
WriteLine(" PdbInfo: {0}", module.Pdb?.ToString() ?? "<none>");
-
- DataTarget.DataReader.GetVersionInfo(module.ImageBase, out VersionInfo version);
- WriteLine(" Version: {0}", version);
+ VersionInfo? version = null;
+ try
+ {
+ version = ModuleService.GetModuleFromBaseAddress(module.ImageBase).Version;
+ }
+ catch (DiagnosticsException)
+ {
+ }
+ WriteLine(" Version: {0}", version?.ToString() ?? "<none>");
}
else
{
+++ /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.Repl;
-using System.CommandLine;
-using System.Threading.Tasks;
-
-namespace Microsoft.Diagnostics.Tools.Dump
-{
- [Command(Name = "exit", Help = "Exit interactive mode.")]
- [CommandAlias(Name = "quit")]
- [CommandAlias(Name = "q")]
- public class ExitCommand : CommandBase
- {
- public override void Invoke()
- {
- Console.Exit();
- }
- }
-}
+++ /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.Repl;
-using System.CommandLine;
-using System.CommandLine.Help;
-
-namespace Microsoft.Diagnostics.Tools.Dump
-{
- [Command(Name = "help", Help = "Display help for a command.")]
- [CommandAlias(Name = "soshelp")]
- public class HelpCommand : CommandBase
- {
- [Argument(Help = "Command to find help.")]
- public string Command { get; set; }
-
- public CommandProcessor CommandProcessor { get; set; }
-
- public IHelpBuilder HelpBuilder { get; set; }
-
- public override void Invoke()
- {
- Command command = CommandProcessor.GetCommand(Command);
- if (command != null) {
- HelpBuilder.Write(command);
- }
- else {
- WriteLineError($"Help for {Command} not found.");
- }
- }
- }
-}
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.DebugServices;
-using Microsoft.Diagnostics.Repl;
using System;
-using System.CommandLine;
using System.Diagnostics;
-namespace Microsoft.Diagnostics.Tools.Dump
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "logging", Help = "Enable/disable internal logging")]
+ [Command(Name = "logging", Help = "Enable/disable internal logging", Platform = CommandPlatform.Global)]
public class LoggingCommand : CommandBase
{
[Option(Name = "enable", Help = "Enable internal logging.")]
{
if (Enable) {
if (Trace.Listeners[ListenerName] == null) {
- Trace.Listeners.Add(new LoggingListener(Console));
+ Trace.Listeners.Add(new LoggingListener());
+ Trace.AutoFlush = true;
}
}
else if (Disable) {
class LoggingListener : TraceListener
{
- private readonly IConsoleService _console;
-
- internal LoggingListener(IConsoleService console)
+ internal LoggingListener()
: base(ListenerName)
{
- _console = console;
}
public override void Write(string message)
{
- _console.Write(message);
+ System.Console.Write(message);
}
public override void WriteLine(string message)
{
- _console.Write(message + Environment.NewLine);
+ System.Console.WriteLine(message);
}
}
}
// 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.Repl;
-using Microsoft.Diagnostics.Runtime;
-using System.CommandLine;
+using Microsoft.Diagnostics.DebugServices;
using System.Linq;
-using System.Threading.Tasks;
-namespace Microsoft.Diagnostics.Tools.Dump
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "modules", Help = "Displays the native modules in the process.")]
- [CommandAlias(Name = "lm")]
+ [Command(Name = "modules", Aliases = new string[] { "lm" }, Help = "Displays the native modules in the process.")]
public class ModulesCommand : CommandBase
{
- [Option(Name = "--verbose", Help = "Displays more details.")]
- [OptionAlias(Name = "-v")]
+ [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")]
public bool Verbose { get; set; }
- public DataTarget DataTarget { get; set; }
+ public IModuleService ModuleService { get; set; }
public override void Invoke()
{
- foreach (ModuleInfo module in DataTarget.EnumerateModules())
+ ulong totalSize = 0;
+ foreach (IModule module in ModuleService.EnumerateModules())
{
+ totalSize += module.ImageSize;
if (Verbose)
{
WriteLine("{0}", module.FileName);
- WriteLine(" Address: {0:X16}", module.ImageBase);
- WriteLine(" IsManaged: {0}", module.IsManaged);
- WriteLine(" FileSize: {0:X8}", module.IndexFileSize);
- WriteLine(" TimeStamp: {0:X8}", module.IndexTimeStamp);
- WriteLine(" Version: {0}", module.Version);
- WriteLine(" PdbInfo: {0}", module.Pdb?.ToString() ?? "<none>");
- if (module.BuildId != null) {
- WriteLine(" BuildId: {0}", string.Concat(module.BuildId.Select((b) => b.ToString("x2"))));
+ WriteLine(" Address: {0:X16}", module.ImageBase);
+ WriteLine(" ImageSize: {0:X8}", module.ImageSize);
+ WriteLine(" IsPEImage: {0}", module.IsPEImage);
+ WriteLine(" IsManaged: {0}", module.IsManaged);
+ WriteLine(" IsFileLayout: {0}", module.IsFileLayout?.ToString() ?? "<unknown>");
+ WriteLine(" FileSize: {0:X8}", module.IndexFileSize);
+ WriteLine(" TimeStamp: {0:X8}", module.IndexTimeStamp);
+ WriteLine(" Version: {0}", module.Version?.ToString() ?? "<none>");
+ string versionString = module.VersionString;
+ if (!string.IsNullOrEmpty(versionString)) {
+ WriteLine(" {0}", versionString);
}
+ WriteLine(" PdbInfo: {0}", module.PdbInfo?.ToString() ?? "<none>");
+ WriteLine(" BuildId: {0}", !module.BuildId.IsDefaultOrEmpty ? string.Concat(module.BuildId.Select((b) => b.ToString("x2"))) : "<none>");
}
else
{
- WriteLine("{0:X16} {1:X8} {2}", module.ImageBase, module.IndexFileSize, module.FileName);
+ WriteLine("{0:X16} {1:X8} {2}", module.ImageBase, module.ImageSize, module.FileName);
}
}
+ WriteLine("Total image size: {0}", totalSize);
}
}
}
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.DebugServices;
-using Microsoft.Diagnostics.Repl;
using System;
-namespace Microsoft.Diagnostics.Tools.Dump
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "registers", Help = "Displays the thread's registers.")]
- [CommandAlias(Name = "r")]
+ [Command(Name = "registers", Aliases = new string[] { "r" }, Help = "Displays the thread's registers.")]
public class RegistersCommand : CommandBase
{
- [Argument(Help = "The thread index to display, otherwise use the current thread.")]
- public int? ThreadIndex { get; set; } = null;
-
- public AnalyzeContext AnalyzeContext { get; set; }
-
public IThreadService ThreadService { get; set; }
+ public IThread CurrentThread { get; set; }
+
public override void Invoke()
{
- uint threadId;
- if (ThreadIndex.HasValue)
- {
- threadId = ThreadService.GetThreadInfoFromIndex(ThreadIndex.Value).ThreadId;
- }
- else
+ IThread thread = CurrentThread;
+ if (thread == null)
{
- if (AnalyzeContext.CurrentThreadId.HasValue)
- {
- threadId = AnalyzeContext.CurrentThreadId.Value;
- }
- else
- {
- throw new InvalidOperationException($"No current thread");
- }
+ throw new InvalidOperationException($"No current thread");
}
foreach (RegisterInfo register in ThreadService.Registers)
{
- if (ThreadService.GetRegisterValue(threadId, register.RegisterIndex, out ulong value))
+ if (thread.GetRegisterValue(register.RegisterIndex, out ulong value))
{
switch (register.RegisterSize)
{
--- /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.Linq;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "runtimes", Help = "List the runtimes in the target or change the default runtime.")]
+ public class RuntimesCommand: CommandBase
+ {
+ public IRuntimeService RuntimeService { get; set; }
+
+ [Option(Name = "-netfx", Help = "Switches to the desktop .NET Framework if exists.")]
+ public bool NetFx { get; set; }
+
+ [Option(Name = "-netcore", Help = "Switches to the .NET Core runtime if exists.")]
+ public bool NetCore { get; set; }
+
+ public override void Invoke()
+ {
+ if (NetFx || NetCore)
+ {
+ string name = NetFx ? "desktop .NET Framework" : ".NET Core";
+ foreach (IRuntime runtime in RuntimeService.Runtimes)
+ {
+ if (NetFx && runtime.RuntimeType == RuntimeType.Desktop ||
+ NetCore && runtime.RuntimeType == RuntimeType.NetCore)
+ {
+ RuntimeService.SetCurrentRuntime(runtime.Id);
+ WriteLine("Switched to {0} runtime successfully", name);
+ return;
+ }
+ }
+ WriteLineError("The {0} runtime is not loaded", name);
+ }
+ else
+ {
+ foreach (IRuntime runtime in RuntimeService.Runtimes)
+ {
+ string current = RuntimeService.Runtimes.Count() > 1 ? runtime == RuntimeService.CurrentRuntime ? "*" : " " : "";
+ Write(current);
+ Write(runtime.ToString());
+ }
+ }
+ }
+ }
+}
// 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.Repl;
-using SOS;
+using Microsoft.Diagnostics.DebugServices;
+using SOS.Hosting;
using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
-namespace Microsoft.Diagnostics.Tools.Dump
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "clrstack", AliasExpansion = "ClrStack", Help = "Provides a stack trace of managed code only.")]
- [Command(Name = "clrthreads", AliasExpansion = "Threads", Help = "List the managed threads running.")]
- [Command(Name = "dbgout", AliasExpansion = "dbgout", Help = "Enable/disable (-off) internal SOS logging.")]
- [Command(Name = "dumpalc", AliasExpansion = "DumpALC", Help = "Displays details about a collectible AssemblyLoadContext into which the specified object is loaded.")]
- [Command(Name = "dumparray", AliasExpansion = "DumpArray", Help = "Displays details about a managed array.")]
- [Command(Name = "dumpasync", AliasExpansion = "DumpAsync", Help = "Displays info about async state machines on the garbage-collected heap.")]
- [Command(Name = "dumpassembly", AliasExpansion = "DumpAssembly", Help = "Displays details about an assembly.")]
- [Command(Name = "dumpclass", AliasExpansion = "DumpClass", Help = "Displays information about a EE class structure at the specified address.")]
- [Command(Name = "dumpdelegate", AliasExpansion = "DumpDelegate", Help = "Displays information about a delegate.")]
- [Command(Name = "dumpdomain", AliasExpansion = "DumpDomain", Help = "Displays information all the AppDomains and all assemblies within the domains.")]
- [Command(Name = "dumpheap", AliasExpansion = "DumpHeap", Help = "Displays info about the garbage-collected heap and collection statistics about objects.")]
- [Command(Name = "dumpil", AliasExpansion = "DumpIL", Help = "Displays the Microsoft intermediate language (MSIL) that is associated with a managed method.")]
- [Command(Name = "dumplog", AliasExpansion = "DumpLog", Help = "Writes the contents of an in-memory stress log to the specified file.")]
- [Command(Name = "dumpmd", AliasExpansion = "DumpMD", Help = "Displays information about a MethodDesc structure at the specified address.")]
- [Command(Name = "dumpmodule", AliasExpansion = "DumpModule", Help = "Displays information about a EE module structure at the specified address.")]
- [Command(Name = "dumpmt", AliasExpansion = "DumpMT", Help = "Displays information about a method table at the specified address.")]
- [Command(Name = "dumpobj", AliasExpansion = "DumpObj", Help = "Displays info about an object at the specified address.")]
- [CommandAlias(Name = "do")]
- [Command(Name = "dumpvc", AliasExpansion = "DumpVC", Help = "Displays info about the fields of a value class.")]
- [Command(Name = "dumpstackobjects", AliasExpansion = "DumpStackObjects", Help = "Displays all managed objects found within the bounds of the current stack.")]
- [CommandAlias(Name = "dso")]
- [Command(Name = "eeheap", AliasExpansion = "EEHeap", Help = "Displays info about process memory consumed by internal runtime data structures.")]
- [Command(Name = "eeversion", AliasExpansion = "EEVersion", Help = "Displays information about the runtime version.")]
- [Command(Name = "finalizequeue", AliasExpansion = "FinalizeQueue", Help = "Displays all objects registered for finalization.")]
- [Command(Name = "gcroot", AliasExpansion = "GCRoot", Help = "Displays info about references (or roots) to an object at the specified address.")]
- [Command(Name = "gcwhere", AliasExpansion = "GCWhere", Help = "Displays the location in the GC heap of the argument passed in.")]
- [Command(Name = "ip2md", AliasExpansion = "IP2MD", Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")]
- [Command(Name = "name2ee", AliasExpansion = "Name2EE", Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")]
- [Command(Name = "printexception", AliasExpansion = "PrintException", Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")]
- [CommandAlias(Name = "pe")]
- [Command(Name = "sosstatus", AliasExpansion = "SOSStatus", Help = "Displays the global SOS status.")]
- [Command(Name = "syncblk", AliasExpansion = "SyncBlk", Help = "Displays the SyncBlock holder info.")]
- [Command(Name = "histclear", AliasExpansion = "HistClear", Help = "Releases any resources used by the family of Hist commands.")]
- [Command(Name = "histinit", AliasExpansion = "HistInit", Help = "Initializes the SOS structures from the stress log saved in the debuggee.")]
- [Command(Name = "histobj", AliasExpansion = "HistObj", Help = "Examines all stress log relocation records and displays the chain of garbage collection relocations that may have led to the address passed in as an argument.")]
- [Command(Name = "histobjfind", AliasExpansion = "HistObjFind", Help = "Displays all the log entries that reference an object at the specified address.")]
- [Command(Name = "histroot", AliasExpansion = "HistRoot", Help = "Displays information related to both promotions and relocations of the specified root.")]
- [Command(Name = "setsymbolserver", AliasExpansion = "SetSymbolServer", Help = "Enables the symbol server support.")]
- [Command(Name = "verifyheap", AliasExpansion = "VerifyHeap", Help = "Checks the GC heap for signs of corruption.")]
- [Command(Name = "threadpool", AliasExpansion = "ThreadPool", Help = "Lists basic information about the thread pool.")]
- [Command(Name = "dumprcw", AliasExpansion = "DumpRCW", Platform = CommandPlatform.Windows, Help = "Displays information about a Runtime Callable Wrapper.")]
- [Command(Name = "dumpccw", AliasExpansion = "DumpCCW", Platform = CommandPlatform.Windows, Help = "Displays information about a COM Callable Wrapper.")]
- [Command(Name = "dumppermissionset",AliasExpansion = "DumpPermissionSet", Platform = CommandPlatform.Windows, Help = "Displays a PermissionSet object (debug build only).")]
- [Command(Name = "traverseheap", AliasExpansion = "TraverseHeap", Platform = CommandPlatform.Windows, Help = "Writes out a file in a format understood by the CLR Profiler.")]
- [Command(Name = "analyzeoom", AliasExpansion = "AnalyzeOOM", Platform = CommandPlatform.Windows, Help = "Displays the info of the last OOM occurred on an allocation request to the GC heap.")]
- [Command(Name = "verifyobj", AliasExpansion = "VerifyObj", Platform = CommandPlatform.Windows, Help = "Checks the object for signs of corruption.")]
- [Command(Name = "listnearobj", AliasExpansion = "ListNearObj", Platform = CommandPlatform.Windows, Help = "Displays the object preceding and succeeding the address specified.")]
- [Command(Name = "gcheapstat", AliasExpansion = "GCHeapStat", Platform = CommandPlatform.Windows, Help = "Display various GC heap stats.")]
- [Command(Name = "watsonbuckets", AliasExpansion = "WatsonBuckets", Platform = CommandPlatform.Windows, Help = "Displays the Watson buckets.")]
- [Command(Name = "comstate", AliasExpansion = "COMState", Platform = CommandPlatform.Windows, Help = "Lists the COM apartment model for each thread.")]
- [Command(Name = "gchandles", AliasExpansion = "GCHandles", Platform = CommandPlatform.Windows, Help = "Provides statistics about GCHandles in the process.")]
- [Command(Name = "objsize", AliasExpansion = "ObjSize", Platform = CommandPlatform.Windows, Help = "Lists the sizes of the all the objects found on managed threads.")]
- [Command(Name = "gchandleleaks", AliasExpansion = "GCHandleLeaks", Platform = CommandPlatform.Windows, Help = "Helps in tracking down GCHandle leaks")]
+ [Command(Name = "clrstack", DefaultOptions = "ClrStack", Help = "Provides a stack trace of managed code only.")]
+ [Command(Name = "clrthreads", DefaultOptions = "Threads", Help = "List the managed threads running.")]
+ [Command(Name = "dbgout", DefaultOptions = "dbgout", Help = "Enable/disable (-off) internal SOS logging.")]
+ [Command(Name = "dumpalc", DefaultOptions = "DumpALC", Help = "Displays details about a collectible AssemblyLoadContext into which the specified object is loaded.")]
+ [Command(Name = "dumparray", DefaultOptions = "DumpArray", Help = "Displays details about a managed array.")]
+ [Command(Name = "dumpasync", DefaultOptions = "DumpAsync", Help = "Displays info about async state machines on the garbage-collected heap.")]
+ [Command(Name = "dumpassembly", DefaultOptions = "DumpAssembly", Help = "Displays details about an assembly.")]
+ [Command(Name = "dumpclass", DefaultOptions = "DumpClass", Help = "Displays information about a EE class structure at the specified address.")]
+ [Command(Name = "dumpdelegate", DefaultOptions = "DumpDelegate", Help = "Displays information about a delegate.")]
+ [Command(Name = "dumpdomain", DefaultOptions = "DumpDomain", Help = "Displays information all the AppDomains and all assemblies within the domains.")]
+ [Command(Name = "dumpheap", DefaultOptions = "DumpHeap", Help = "Displays info about the garbage-collected heap and collection statistics about objects.")]
+ [Command(Name = "dumpil", DefaultOptions = "DumpIL", Help = "Displays the Microsoft intermediate language (MSIL) that is associated with a managed method.")]
+ [Command(Name = "dumplog", DefaultOptions = "DumpLog", Help = "Writes the contents of an in-memory stress log to the specified file.")]
+ [Command(Name = "dumpmd", DefaultOptions = "DumpMD", Help = "Displays information about a MethodDesc structure at the specified address.")]
+ [Command(Name = "dumpmodule", DefaultOptions = "DumpModule", Help = "Displays information about a EE module structure at the specified address.")]
+ [Command(Name = "dumpmt", DefaultOptions = "DumpMT", Help = "Displays information about a method table at the specified address.")]
+ [Command(Name = "dumpobj", DefaultOptions = "DumpObj", Aliases = new string[] { "do" }, Help = "Displays info about an object at the specified address.")]
+ [Command(Name = "dumpvc", DefaultOptions = "DumpVC", Help = "Displays info about the fields of a value class.")]
+ [Command(Name = "dumpstackobjects", DefaultOptions = "DumpStackObjects", Aliases = new string[] { "dso" }, Help = "Displays all managed objects found within the bounds of the current stack.")]
+ [Command(Name = "eeheap", DefaultOptions = "EEHeap", Help = "Displays info about process memory consumed by internal runtime data structures.")]
+ [Command(Name = "eeversion", DefaultOptions = "EEVersion", Help = "Displays information about the runtime version.")]
+ [Command(Name = "finalizequeue", DefaultOptions = "FinalizeQueue", Help = "Displays all objects registered for finalization.")]
+ [Command(Name = "gcroot", DefaultOptions = "GCRoot", Help = "Displays info about references (or roots) to an object at the specified address.")]
+ [Command(Name = "gcwhere", DefaultOptions = "GCWhere", Help = "Displays the location in the GC heap of the argument passed in.")]
+ [Command(Name = "ip2md", DefaultOptions = "IP2MD", Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")]
+ [Command(Name = "name2ee", DefaultOptions = "Name2EE", Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")]
+ [Command(Name = "printexception", DefaultOptions = "PrintException", Aliases = new string[] { "pe" }, Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")]
+ [Command(Name = "sosstatus", DefaultOptions = "SOSStatus", Help = "Displays the global SOS status.")]
+ [Command(Name = "syncblk", DefaultOptions = "SyncBlk", Help = "Displays the SyncBlock holder info.")]
+ [Command(Name = "histclear", DefaultOptions = "HistClear", Help = "Releases any resources used by the family of Hist commands.")]
+ [Command(Name = "histinit", DefaultOptions = "HistInit", Help = "Initializes the SOS structures from the stress log saved in the debuggee.")]
+ [Command(Name = "histobj", DefaultOptions = "HistObj", Help = "Examines all stress log relocation records and displays the chain of garbage collection relocations that may have led to the address passed in as an argument.")]
+ [Command(Name = "histobjfind", DefaultOptions = "HistObjFind", Help = "Displays all the log entries that reference an object at the specified address.")]
+ [Command(Name = "histroot", DefaultOptions = "HistRoot", Help = "Displays information related to both promotions and relocations of the specified root.")]
+ [Command(Name = "setsymbolserver", DefaultOptions = "SetSymbolServer", Help = "Enables the symbol server support.")]
+ [Command(Name = "verifyheap", DefaultOptions = "VerifyHeap", Help = "Checks the GC heap for signs of corruption.")]
+ [Command(Name = "threadpool", DefaultOptions = "ThreadPool", Help = "Lists basic information about the thread pool.")]
+ [Command(Name = "soshelp", DefaultOptions = "Help", Help = "Displays help for a specific SOS command.")]
+ [Command(Name = "dumprcw", DefaultOptions = "DumpRCW", Platform = CommandPlatform.Windows, Help = "Displays information about a Runtime Callable Wrapper.")]
+ [Command(Name = "dumpccw", DefaultOptions = "DumpCCW", Platform = CommandPlatform.Windows, Help = "Displays information about a COM Callable Wrapper.")]
+ [Command(Name = "dumppermissionset",DefaultOptions = "DumpPermissionSet", Platform = CommandPlatform.Windows, Help = "Displays a PermissionSet object (debug build only).")]
+ [Command(Name = "traverseheap", DefaultOptions = "TraverseHeap", Platform = CommandPlatform.Windows, Help = "Writes out a file in a format understood by the CLR Profiler.")]
+ [Command(Name = "analyzeoom", DefaultOptions = "AnalyzeOOM", Platform = CommandPlatform.Windows, Help = "Displays the info of the last OOM occurred on an allocation request to the GC heap.")]
+ [Command(Name = "verifyobj", DefaultOptions = "VerifyObj", Platform = CommandPlatform.Windows, Help = "Checks the object for signs of corruption.")]
+ [Command(Name = "listnearobj", DefaultOptions = "ListNearObj", Platform = CommandPlatform.Windows, Help = "Displays the object preceding and succeeding the address specified.")]
+ [Command(Name = "gcheapstat", DefaultOptions = "GCHeapStat", Platform = CommandPlatform.Windows, Help = "Display various GC heap stats.")]
+ [Command(Name = "watsonbuckets", DefaultOptions = "WatsonBuckets", Platform = CommandPlatform.Windows, Help = "Displays the Watson buckets.")]
+ [Command(Name = "comstate", DefaultOptions = "COMState", Platform = CommandPlatform.Windows, Help = "Lists the COM apartment model for each thread.")]
+ [Command(Name = "gchandles", DefaultOptions = "GCHandles", Platform = CommandPlatform.Windows, Help = "Provides statistics about GCHandles in the process.")]
+ [Command(Name = "objsize", DefaultOptions = "ObjSize", Platform = CommandPlatform.Windows, Help = "Lists the sizes of the all the objects found on managed threads.")]
+ [Command(Name = "gchandleleaks", DefaultOptions = "GCHandleLeaks", Platform = CommandPlatform.Windows, Help = "Helps in tracking down GCHandle leaks")]
public class SOSCommand : CommandBase
{
[Argument(Name = "arguments", Help = "Arguments to SOS command.")]
public override void Invoke()
{
try {
- string arguments = null;
- if (Arguments != null && Arguments.Length > 0) {
- arguments = string.Concat(Arguments.Select((arg) => arg + " "));
- }
- SOSHost.ExecuteCommand(AliasExpansion, arguments);
+ Debug.Assert(Arguments != null && Arguments.Length > 0);
+ string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " "));
+ SOSHost.ExecuteCommand(Arguments[0], arguments);
}
catch (Exception ex) when (ex is FileNotFoundException || ex is EntryPointNotFoundException || ex is InvalidOperationException) {
WriteLineError(ex.Message);
[HelpInvoke]
public void InvokeHelp()
{
- SOSHost.ExecuteCommand("Help", AliasExpansion);
+ SOSHost.ExecuteCommand("Help", Arguments[0]);
}
}
}
// See the LICENSE file in the project root for more information.
using Microsoft.Diagnostics.DebugServices;
-using Microsoft.Diagnostics.Repl;
-using System.CommandLine;
+using System.IO;
-namespace Microsoft.Diagnostics.Tools.Dump
+namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "setclrpath", Help = "Set the path to load coreclr DAC/DBI files.")]
public class SetClrPath: CommandBase
{
- public AnalyzeContext AnalyzeContext { get; set; }
+ public IRuntimeService RuntimeService { get; set; }
- [Argument(Name = "clrpath", Help = "Runtime directory path.")]
+ [Argument(Name = "path", Help = "Runtime directory path.")]
public string Argument { get; set; }
+ [Option(Name = "--clear", Aliases = new string[] { "-c" }, Help = "Clears the runtime directory path.")]
+ public bool Clear { get; set; }
+
public override void Invoke()
{
- if (Argument == null)
+ if (RuntimeService == null)
+ {
+ throw new DiagnosticsException("Runtime service required");
+ }
+ if (Clear)
+ {
+ RuntimeService.RuntimeModuleDirectory = null;
+ }
+ else if (Argument == null)
{
- WriteLine("Load path for DAC/DBI: '{0}'", AnalyzeContext.RuntimeModuleDirectory ?? "<none>");
+ WriteLine("Load path for DAC/DBI: '{0}'", RuntimeService.RuntimeModuleDirectory ?? "<none>");
}
else
{
- AnalyzeContext.RuntimeModuleDirectory = Argument;
- WriteLine("Set load path for DAC/DBI to '{0}'", AnalyzeContext.RuntimeModuleDirectory);
+ RuntimeService.RuntimeModuleDirectory = Path.GetFullPath(Argument);
+ WriteLine("Set load path for DAC/DBI to '{0}'", RuntimeService.RuntimeModuleDirectory);
}
}
}
+++ /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 Microsoft.Diagnostics.Repl;
-
-namespace Microsoft.Diagnostics.Tools.Dump
-{
- [Command(Name = "setthread", Help = "Sets or displays the current thread for the SOS commands.")]
- [CommandAlias(Name = "threads")]
- public class SetThreadCommand : CommandBase
- {
- [Argument(Help = "The thread index or id to set, otherwise displays the list of threads.")]
- public int? Thread { get; set; } = null;
-
- [Option(Name = "--tid", Help = "<thread> is an OS thread id.")]
- [OptionAlias(Name = "-t")]
- public bool ThreadId { get; set; }
-
- public AnalyzeContext AnalyzeContext { get; set; }
-
- public IThreadService ThreadService { get; set; }
-
- public override void Invoke()
- {
- if (Thread.HasValue)
- {
- ThreadInfo threadInfo;
- if (ThreadId)
- {
- threadInfo = ThreadService.GetThreadInfoFromId((uint)Thread.Value);
- }
- else
- {
- threadInfo = ThreadService.GetThreadInfoFromIndex(Thread.Value);
- }
- AnalyzeContext.CurrentThreadId = threadInfo.ThreadId;
- }
- else
- {
- uint currentThreadId = AnalyzeContext.CurrentThreadId.GetValueOrDefault(uint.MaxValue);
- foreach (ThreadInfo thread in ThreadService.EnumerateThreads())
- {
- WriteLine("{0}{1} 0x{2:X4} ({2})", thread.ThreadId == currentThreadId ? "*" : " ", thread.ThreadIndex, thread.ThreadId);
- }
- }
- }
- }
-}
--- /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;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "threads", Aliases = new string[] { "setthread" }, Help = "Displays threads or sets the current thread.")]
+ public class ThreadsCommand : CommandBase
+ {
+ [Argument(Help = "The thread index or id to set, otherwise displays the list of threads.")]
+ public uint? Thread { get; set; } = null;
+
+ [Option(Name = "--tid", Aliases = new string[] { "-t" }, Help = "<thread> is an OS thread id.")]
+ public bool ThreadId { get; set; }
+
+ [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")]
+ public bool Verbose { get; set; }
+
+ public IThreadService ThreadService { get; set; }
+
+ public override void Invoke()
+ {
+ if (Thread.HasValue)
+ {
+ IThread thread;
+ if (ThreadId)
+ {
+ thread = ThreadService.GetThreadInfoFromId(Thread.Value);
+ }
+ else
+ {
+ thread = ThreadService.GetThreadInfoFromIndex(unchecked((int)Thread.Value));
+ }
+ ThreadService.CurrentThreadId = thread.ThreadId;
+ }
+ else
+ {
+ uint currentThreadId = ThreadService.CurrentThreadId.GetValueOrDefault(uint.MaxValue);
+ foreach (IThread thread in ThreadService.EnumerateThreads())
+ {
+ WriteLine("{0}{1} 0x{2:X4} ({2})", thread.ThreadId == currentThreadId ? "*" : " ", thread.ThreadIndex, thread.ThreadId);
+ if (Verbose)
+ {
+ thread.GetRegisterValue(ThreadService.InstructionPointerIndex, out ulong ip);
+ thread.GetRegisterValue(ThreadService.StackPointerIndex, out ulong sp);
+ thread.GetRegisterValue(ThreadService.FramePointerIndex, out ulong fp);
+ WriteLine(" IP 0x{0:X16}", ip);
+ WriteLine(" SP 0x{0:X16}", sp);
+ WriteLine(" FP 0x{0:X16}", fp);
+ WriteLine(" TEB 0x{0:X16}", thread.GetThreadTeb());
+ }
+ }
+ }
+ }
+ }
+}
// 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 Microsoft.Diagnostics.Runtime;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using Microsoft.Diagnostics.DebugServices;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public class ClrMDHelper
{
private readonly ClrRuntime _clr;
private readonly ClrHeap _heap;
- public ClrMDHelper(ServiceProvider provider)
+ public ClrMDHelper(ClrRuntime clr)
{
- _clr = provider.GetService<ClrRuntime>();
+ _clr = clr ?? throw new DiagnosticsException("No CLR runtime set");
_heap = _clr.Heap;
}
// 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.Repl;
+using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
using System;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "dumpconcurrentdictionary", Help = "Display concurrent dictionary content.")]
- [CommandAlias(Name = "dcd")]
+ [Command(Name = "dumpconcurrentdictionary", Aliases = new string[] { "dcd" }, Help = "Display concurrent dictionary content.")]
public class DumpConcurrentDictionaryCommand : ExtensionCommandBase
{
[Argument(Help = "The address of a ConcurrentDictionary object.")]
return str.Substring(0, nbMaxChars) + "...";
}
}
-}
\ No newline at end of file
+}
// 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 Microsoft.Diagnostics.Repl;
+using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
+using System;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "dumpconcurrentqueue", Help = "Display concurrent queue content.")]
- [CommandAlias(Name = "dcq")]
+ [Command(Name = "dumpconcurrentqueue", Aliases = new string[] { "dcq" }, Help = "Display concurrent queue content.")]
public class DumpConcurrentQueueCommand : ExtensionCommandBase
{
[Argument(Help = "The address of a ConcurrentQueue object.")]
-
public string Address { get; set; }
public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
if (string.IsNullOrEmpty(Address))
using System.Collections.Generic;
using System.Linq;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public class DumpGen
// 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.Diagnostic.Tools.Dump.ExtensionCommands;
-using Microsoft.Diagnostics.Repl;
+using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
-using System;
using System.Collections.Generic;
-using System.CommandLine;
-namespace Microsoft.Diagnostics.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "dumpgen", Help = "Displays heap content for the specified generation.")]
- [CommandAlias(Name = "dg")]
+ [Command(Name = "dumpgen", Aliases = new string[] { "dg" }, Help = "Displays heap content for the specified generation.")]
public class DumpGenCommand : ExtensionCommandBase
{
private const string statsHeader32bits = " MT Count TotalSize Class Name";
using Microsoft.Diagnostics.Runtime;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public class DumpGenStats
{
// 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.Repl;
+using Microsoft.Diagnostics.DebugServices;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public abstract class ExtensionCommandBase : CommandBase
{
// 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.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public enum GCGeneration
{
// 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 Microsoft.Diagnostics.DebugServices;
using ParallelStacks.Runtime;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public class MonoColorConsoleRenderer : RendererBase
{
// 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 Microsoft.Diagnostics.Repl;
+using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
using ParallelStacks.Runtime;
+using System;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "parallelstacks", Help = "Display merged threads stack a la Visual Studio 'Parallel Stacks' panel.")]
- [CommandAlias(Name = "pstacks")]
+ [Command(Name = "parallelstacks", Aliases = new string[] { "pstacks" }, Help = "Display merged threads stack a la Visual Studio 'Parallel Stacks' panel.")]
public class ParallelStacksCommand : ExtensionCommandBase
{
public ClrRuntime Runtime { get; set; }
- [Option(Name = "--allThreads", Help = "Displays all threads per group instead of at most 4 by default.")]
- [OptionAlias(Name = "-a")]
+ [Option(Name = "--allthreads", Aliases = new string[] { "-a" }, Help = "Displays all threads per group instead of at most 4 by default.")]
public bool AllThreads { get; set; }
-
public override void Invoke()
{
var ps = ParallelStacks.Runtime.ParallelStack.Build(Runtime);
// 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;
-using Microsoft.Diagnostics.Repl;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "taskstate", Help = "Display a Task state in a human readable format.")]
- [CommandAlias(Name = "tks")]
+ [Command(Name = "taskstate", Aliases = new string[] { "tks" }, Help = "Display a Task state in a human readable format.")]
public class TaskStateCommand : ExtensionCommandBase
{
[Argument(Help = "The Task instance address.")]
public string Address { get; set; }
- [Option(Name = "--value", Help = "<value> is the value of a Task m_stateFlags field.")]
- [OptionAlias(Name = "-v")]
+ [Option(Name = "--value", Aliases = new string[] { "-v" }, Help = "<value> is the value of a Task m_stateFlags field.")]
public ulong? Value { get; set; }
-
public override void Invoke()
{
if (string.IsNullOrEmpty(Address) && !Value.HasValue)
// 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.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public class ThreadPoolItem
{
// 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;
using System.Collections.Generic;
using System.Linq;
-using Microsoft.Diagnostics.Repl;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "threadpoolqueue", Help = "Display queued ThreadPool work items.")]
- [CommandAlias(Name = "tpq")]
+ [Command(Name = "threadpoolqueue", Aliases = new string[] { "tpq" }, Help = "Display queued ThreadPool work items.")]
public class ThreadPoolQueueCommand : ExtensionCommandBase
{
public override void Invoke()
// 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.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public enum ThreadRoot
{
// 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.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
public class TimerInfo
{
// 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;
using System.Collections.Generic;
using System.Linq;
-using Microsoft.Diagnostics.Repl;
-namespace Microsoft.Diagnostic.Tools.Dump.ExtensionCommands
+namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "timerinfo", Help = "Display running timers details.")]
- [CommandAlias(Name = "ti")]
+ [Command(Name = "timerinfo", Aliases = new string[] { "ti" }, Help = "Display running timers details.")]
public class TimersCommand : ExtensionCommandBase
{
public override void Invoke()
</ItemGroup>
<ItemGroup>
- <Compile Include="..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
- <Compile Include="..\Common\Commands\ProcessStatus.cs" Link="ProcessStatus.cs" />
- <Compile Include="..\Common\Commands\Utils.cs" Link="Utils.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)..\Common\CommandExtensions.cs" Link="CommandExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)..\Common\Commands\ProcessStatus.cs" Link="ProcessStatus.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)..\Common\Commands\Utils.cs" Link="Utils.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.Hosting\SOS.Hosting.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\SOS\SOS.NETCore\SOS.NETCore.csproj" />
- <ProjectReference Include="..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices.Implementation\Microsoft.Diagnostics.DebugServices.Implementation.csproj" />
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)..\..\sos-packaging.props" />