From 3beea4e111824d75f915a0d6c1585a035032b3ad Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Wed, 11 Dec 2019 15:42:59 -0800 Subject: [PATCH] Add setclrpath command to dotnet-dump on linux. Mentioned in https://github.com/dotnet/diagnostics/issues/624 --- .../AnalyzeContext.cs | 5 ++ .../Command/Attributes.cs | 53 ++++++++++- .../Command/CommandProcessor.cs | 89 ++++++++++--------- src/SOS/SOS.Hosting/LLDBServices.cs | 12 ++- src/SOS/SOS.Hosting/SOSHost.cs | 22 ++--- src/SOS/Strike/hostcoreclr.cpp | 2 +- .../dotnet-dump/Commands/SetClrPathCommand.cs | 32 +++++++ 7 files changed, 155 insertions(+), 60 deletions(-) create mode 100644 src/Tools/dotnet-dump/Commands/SetClrPathCommand.cs diff --git a/src/Microsoft.Diagnostics.DebugServices/AnalyzeContext.cs b/src/Microsoft.Diagnostics.DebugServices/AnalyzeContext.cs index e1f4e27b80..80f7b15bf2 100644 --- a/src/Microsoft.Diagnostics.DebugServices/AnalyzeContext.cs +++ b/src/Microsoft.Diagnostics.DebugServices/AnalyzeContext.cs @@ -24,5 +24,10 @@ public AnalyzeContext() /// Cancellation token for current command /// public CancellationToken CancellationToken { get; set; } + + /// + /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.) + /// + public string RuntimeModuleDirectory { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.Diagnostics.Repl/Command/Attributes.cs b/src/Microsoft.Diagnostics.Repl/Command/Attributes.cs index 2264e6241d..5e936c8a8d 100644 --- a/src/Microsoft.Diagnostics.Repl/Command/Attributes.cs +++ b/src/Microsoft.Diagnostics.Repl/Command/Attributes.cs @@ -3,11 +3,24 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.InteropServices; namespace Microsoft.Diagnostics.Repl { /// - /// Base command option attribute. + /// OS Platforms to add command + /// + [Flags] + public enum CommandPlatform : byte + { + All = 0x00, + Windows = 0x01, + Linux = 0x02, + OSX = 0x04, + } + + /// + /// Base command, option and argument class. /// public class BaseAttribute : Attribute { @@ -22,11 +35,45 @@ public class BaseAttribute : Attribute public string Help; } + /// + /// Base command and command alias class. + /// + public class CommandBaseAttribute : BaseAttribute + { + /// + /// Optional OS platform for the command + /// + public CommandPlatform Platform = CommandPlatform.All; + + /// + /// Returns true if the command should be added. + /// + public bool IsValidPlatform() + { + if (Platform != CommandPlatform.All) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return (Platform & CommandPlatform.Windows) != 0; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return (Platform & CommandPlatform.Linux) != 0; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return (Platform & CommandPlatform.OSX) != 0; + } + } + return true; + } + } + /// /// Marks the class as a Command. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class CommandAttribute : BaseAttribute + public class CommandAttribute : CommandBaseAttribute { /// /// Sets the value of the CommandBase.AliasExpansion when the command is executed. @@ -38,7 +85,7 @@ public class CommandAttribute : BaseAttribute /// Adds an alias to the previous command attribute /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class CommandAliasAttribute : BaseAttribute + public class CommandAliasAttribute : CommandBaseAttribute { } diff --git a/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs b/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs index 4a2791e17d..a637d9da6c 100644 --- a/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs +++ b/src/Microsoft.Diagnostics.Repl/Command/CommandProcessor.cs @@ -116,63 +116,70 @@ private void BuildCommands(CommandLineBuilder rootBuilder, Type type) { if (baseAttribute is CommandAttribute commandAttribute) { - command = new Command(commandAttribute.Name, commandAttribute.Help); - var properties = new List<(PropertyInfo, Option)>(); - var arguments = new List<(PropertyInfo, Argument)>(); - - foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) + if (commandAttribute.IsValidPlatform()) { - 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 + command = new Command(commandAttribute.Name, commandAttribute.Help); + var properties = new List<(PropertyInfo, Option)>(); + var arguments = new List<(PropertyInfo, Argument)>(); + + foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite)) { - 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) { - var option = new Option(optionAttribute.Name ?? BuildAlias(property.Name), optionAttribute.Help) { - Argument = new Argument { ArgumentType = property.PropertyType } - }; - command.AddOption(option); - properties.Add((property, option)); + IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne; - foreach (var optionAliasAttribute in (OptionAliasAttribute[])property.GetCustomAttributes(typeof(OptionAliasAttribute), inherit: false)) - { - option.AddAlias(optionAliasAttribute.Name); - } + 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 { - // If not an option, add as just a settable properties - properties.Add((property, null)); + 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)); + } } } - } - var handler = new Handler(this, commandAttribute.AliasExpansion, arguments, properties, type); - _commandHandlers.Add(command.Name, handler); - command.Handler = handler; + var handler = new Handler(this, commandAttribute.AliasExpansion, arguments, properties, type); + _commandHandlers.Add(command.Name, handler); + command.Handler = handler; - rootBuilder.AddCommand(command); + rootBuilder.AddCommand(command); + } } if (baseAttribute is CommandAliasAttribute commandAliasAttribute) { - if (command == null) { - throw new ArgumentException($"No previous CommandAttribute for this CommandAliasAttribute: {type.Name}"); + if (commandAliasAttribute.IsValidPlatform()) + { + if (command == null) + { + throw new ArgumentException($"No previous CommandAttribute for this CommandAliasAttribute: {type.Name}"); + } + command.AddAlias(commandAliasAttribute.Name); } - command.AddAlias(commandAliasAttribute.Name); } } } diff --git a/src/SOS/SOS.Hosting/LLDBServices.cs b/src/SOS/SOS.Hosting/LLDBServices.cs index 554b43b3fb..49a5e491b5 100644 --- a/src/SOS/SOS.Hosting/LLDBServices.cs +++ b/src/SOS/SOS.Hosting/LLDBServices.cs @@ -92,13 +92,17 @@ public LLDBServices(SOSHost soshost) string GetCoreClrDirectory( IntPtr self) { - foreach (ModuleInfo module in _soshost.DataReader.EnumerateModules()) + if (_soshost.AnalyzeContext.RuntimeModuleDirectory == null) { - if (SOSHost.IsRuntimeModule(module)) { - return Path.GetDirectoryName(module.FileName) + Path.DirectorySeparatorChar; + foreach (ModuleInfo module in _soshost.DataReader.EnumerateModules()) + { + if (SOSHost.IsRuntimeModule(module)) + { + _soshost.AnalyzeContext.RuntimeModuleDirectory = Path.GetDirectoryName(module.FileName) + Path.DirectorySeparatorChar; + } } } - return null; + return _soshost.AnalyzeContext.RuntimeModuleDirectory; } int VirtualUnwind( diff --git a/src/SOS/SOS.Hosting/SOSHost.cs b/src/SOS/SOS.Hosting/SOSHost.cs index b6792a4ea8..909f2b1397 100644 --- a/src/SOS/SOS.Hosting/SOSHost.cs +++ b/src/SOS/SOS.Hosting/SOSHost.cs @@ -155,13 +155,13 @@ struct SOSNetCoreCallbacks GetExpressionDelegate = SOSHost.GetExpression, }; - internal readonly IDataReader DataReader; - const string DesktopRuntimeModuleName = "clr"; private static readonly string s_coreclrModuleName; - private readonly AnalyzeContext _analyzeContext; + internal readonly IDataReader DataReader; + internal readonly AnalyzeContext AnalyzeContext; + private readonly RegisterService _registerService; private readonly MemoryService _memoryService; private readonly IConsoleService _console; @@ -203,7 +203,7 @@ public SOSHost(IServiceProvider serviceProvider) DataTarget dataTarget = serviceProvider.GetService(); DataReader = dataTarget.DataReader; _console = serviceProvider.GetService(); - _analyzeContext = serviceProvider.GetService(); + AnalyzeContext = serviceProvider.GetService(); _memoryService = serviceProvider.GetService(); _registerService = serviceProvider.GetService(); _versionCache = new ReadVirtualCache(_memoryService); @@ -343,7 +343,7 @@ internal static UIntPtr GetExpression( internal int GetInterrupt( IntPtr self) { - return _analyzeContext.CancellationToken.IsCancellationRequested ? S_OK : E_FAIL; + return AnalyzeContext.CancellationToken.IsCancellationRequested ? S_OK : E_FAIL; } internal int OutputVaList( @@ -877,7 +877,7 @@ internal int GetThreadContext( IntPtr context, uint contextSize) { - uint threadId = (uint)_analyzeContext.CurrentThreadId; + uint threadId = (uint)AnalyzeContext.CurrentThreadId; byte[] registerContext = _registerService.GetThreadContext(threadId); if (registerContext == null) { return E_FAIL; @@ -934,7 +934,7 @@ internal int GetCurrentThreadId( IntPtr self, out uint id) { - return GetThreadIdBySystemId(self, (uint)_analyzeContext.CurrentThreadId, out id); + return GetThreadIdBySystemId(self, (uint)AnalyzeContext.CurrentThreadId, out id); } internal int SetCurrentThreadId( @@ -944,7 +944,7 @@ internal int SetCurrentThreadId( try { unchecked { - _analyzeContext.CurrentThreadId = (int)DataReader.EnumerateAllThreads().ElementAt((int)id); + AnalyzeContext.CurrentThreadId = (int)DataReader.EnumerateAllThreads().ElementAt((int)id); } } catch (ArgumentOutOfRangeException) @@ -958,7 +958,7 @@ internal int GetCurrentThreadSystemId( IntPtr self, out uint sysId) { - sysId = (uint)_analyzeContext.CurrentThreadId; + sysId = (uint)AnalyzeContext.CurrentThreadId; return S_OK; } @@ -1014,7 +1014,7 @@ internal unsafe int GetCurrentThreadTeb( IntPtr self, ulong* offset) { - uint threadId = (uint)_analyzeContext.CurrentThreadId; + uint threadId = (uint)AnalyzeContext.CurrentThreadId; ulong teb = DataReader.GetThreadTeb(threadId); Write(offset, teb); return S_OK; @@ -1102,7 +1102,7 @@ internal int GetRegister( int index, out ulong value) { - uint threadId = (uint)_analyzeContext.CurrentThreadId; + uint threadId = (uint)AnalyzeContext.CurrentThreadId; if (!_registerService.GetRegisterValue(threadId, index, out value)) { return E_FAIL; } diff --git a/src/SOS/Strike/hostcoreclr.cpp b/src/SOS/Strike/hostcoreclr.cpp index a206226e61..67771d077b 100644 --- a/src/SOS/Strike/hostcoreclr.cpp +++ b/src/SOS/Strike/hostcoreclr.cpp @@ -235,7 +235,7 @@ HRESULT GetRuntimeDirectory(std::string& runtimeDirectory) LPCSTR directory = g_ExtServices->GetCoreClrDirectory(); if (directory == NULL) { - ExtErr("Error: Runtime module (%s) not loaded yet\n", NETCORE_RUNTIME_DLL_NAME_A); + ExtErr("Error: Runtime module (%s) not loaded yet\n", GetRuntimeDllName()); return E_FAIL; } if (!GetAbsolutePath(directory, runtimeDirectory)) diff --git a/src/Tools/dotnet-dump/Commands/SetClrPathCommand.cs b/src/Tools/dotnet-dump/Commands/SetClrPathCommand.cs new file mode 100644 index 0000000000..e59fd3bef8 --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/SetClrPathCommand.cs @@ -0,0 +1,32 @@ +// 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; +using System.CommandLine; + +namespace Microsoft.Diagnostics.Tools.Dump +{ + [Command(Name = "setclrpath", Platform = CommandPlatform.Linux | CommandPlatform.OSX, Help = "Set the path to load coreclr DAC/DBI files.")] + public class SetClrPath: CommandBase + { + public AnalyzeContext AnalyzeContext { get; set; } + + [Argument(Name = "clrpath", Help = "Runtime directory path.")] + public string Argument { get; set; } + + public override void Invoke() + { + if (Argument == null) + { + WriteLine("Load path for DAC/DBI: '{0}'", AnalyzeContext.RuntimeModuleDirectory ?? ""); + } + else + { + AnalyzeContext.RuntimeModuleDirectory = Argument; + WriteLine("Set load path for DAC/DBI to '{0}'", AnalyzeContext.RuntimeModuleDirectory); + } + } + } +}