From bdc42d422aad4f9e390e5bc6a541a4e79fd33d21 Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Sat, 16 Jul 2022 12:29:57 -0700 Subject: [PATCH] Allow sos to be used consistently across all debuggers/hosts Issue https://github.com/dotnet/diagnostics/issues/2998 Renamed CommandPlatform to CommandFlags Add DAC EnumMemoryRegion `enummemory` test command Don't fail if datatarget's ReadVirtual is passed 0 size --- .../CommandService.cs | 113 ++++++------- .../Utilities.cs | 61 +++++++ .../CommandAttributes.cs | 13 +- .../DumpAsyncCommand.cs | 2 +- .../Host/LoggingCommand.cs | 2 +- .../Host/SetSymbolServerCommand.cs | 7 +- src/Microsoft.Diagnostics.Repl/ExitCommand.cs | 2 +- src/Microsoft.Diagnostics.Repl/HelpCommand.cs | 2 +- src/SOS/SOS.Extensions/HostServices.cs | 33 +++- src/SOS/SOS.Hosting/Commands/SOSCommand.cs | 16 +- .../SOS.Hosting/CorDebugDataTargetWrapper.cs | 14 +- src/SOS/SOS.Hosting/DataTargetWrapper.cs | 18 +- src/SOS/SOS.Hosting/SOSLibrary.cs | 2 +- src/SOS/SOS.UnitTests/SOSRunner.cs | 33 ++-- .../Scripts/OtherCommands.script | 6 +- .../Scripts/StackAndOtherTests.script | 2 - .../SOS.UnitTests/Scripts/StackTests.script | 2 - src/SOS/SOS.UnitTests/Scripts/WebApp.script | 8 + src/SOS/Strike/Strike.vcxproj | 2 +- src/SOS/Strike/sos.def | 2 + src/SOS/Strike/sos_unixexports.src | 7 +- src/SOS/Strike/sosdocsunix.txt | 8 +- src/SOS/Strike/strike.cpp | 154 +++++++++++++++--- src/SOS/inc/hostservices.h | 10 +- src/SOS/lldbplugin/services.cpp | 72 +++++++- src/SOS/lldbplugin/services.h | 4 + src/SOS/lldbplugin/soscommand.cpp | 44 ++--- src/Tools/dotnet-dump/Analyzer.cs | 1 + .../dotnet-dump/Commands/ReadMemoryCommand.cs | 2 +- src/Tools/dotnet-dump/Commands/SOSCommand.cs | 60 +++++++ src/shared/inc/dumpcommon.h | 136 ++++++++++++++++ 31 files changed, 649 insertions(+), 189 deletions(-) create mode 100644 src/Tools/dotnet-dump/Commands/SOSCommand.cs create mode 100644 src/shared/inc/dumpcommon.h diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs index bfb2bc5720..7585f33b90 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CommandService.cs @@ -42,8 +42,8 @@ public CommandService(string commandPrompt = null) /// /// command line text /// services for the command - /// exit code - public int Execute(string commandLine, IServiceProvider services) + /// true success, false failure + public bool Execute(string commandLine, IServiceProvider services) { // Parse the command line and invoke the command ParseResult parseResult = Parser.Parse(commandLine); @@ -70,7 +70,7 @@ public int Execute(string commandLine, IServiceProvider services) { context.Console.Error.WriteLine($"Command '{command.Name}' needs a target"); } - return 1; + return false; } try { @@ -78,14 +78,27 @@ public int Execute(string commandLine, IServiceProvider services) } catch (Exception ex) { - OnException(ex, context); + if (ex is NullReferenceException || + ex is ArgumentException || + ex is ArgumentNullException || + ex is ArgumentOutOfRangeException || + ex is NotImplementedException) + { + context.Console.Error.WriteLine(ex.ToString()); + } + else + { + context.Console.Error.WriteLine(ex.Message); + } + Trace.TraceError(ex.ToString()); + return false; } } } } context.InvocationResult?.Apply(context); - return context.ResultCode; + return context.ResultCode == 0; } /// @@ -137,6 +150,13 @@ public bool DisplayHelp(string commandName, IServiceProvider services) return true; } + /// + /// Does this command or alias exists? + /// + /// command or alias name + /// true if command exists + public bool IsCommand(string commandName) => _rootBuilder.Command.Children.Contains(commandName); + /// /// Enumerates all the command's name and help /// @@ -149,28 +169,31 @@ public bool DisplayHelp(string commandName, IServiceProvider services) /// function to create command instance public void AddCommands(Type type, Func factory) { - for (Type baseType = type; baseType != null; baseType = baseType.BaseType) + if (type.IsClass) { - if (baseType == typeof(CommandBase)) { - break; - } - var commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false); - foreach (CommandAttribute commandAttribute in commandAttributes) + for (Type baseType = type; baseType != null; baseType = baseType.BaseType) { - if (factory == null) + if (baseType == typeof(CommandBase)) { - // 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()); + break; + } + var commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false); + foreach (CommandAttribute commandAttribute in commandAttributes) + { + if ((commandAttribute.Flags & CommandFlags.Manual) == 0 || factory != null) + { + if (factory == null) + { + factory = (services) => Utilities.InvokeConstructor(type, services, optional: true); + } + CreateCommand(baseType, commandAttribute, factory); + } } - CreateCommand(baseType, commandAttribute, factory); } - } - // Build or re-build parser instance after all these commands and aliases are added - FlushParser(); + // Build or re-build parser instance after all these commands and aliases are added + FlushParser(); + } } private void CreateCommand(Type type, CommandAttribute commandAttribute, Func factory) @@ -234,27 +257,6 @@ private void CreateCommand(Type type, CommandAttribute commandAttribute, Func _parser = null; - private void OnException(Exception ex, InvocationContext context) - { - if (ex is TargetInvocationException) - { - ex = ex.InnerException; - } - if (ex is NullReferenceException || - ex is ArgumentException || - ex is ArgumentNullException || - ex is ArgumentOutOfRangeException || - ex is NotImplementedException) - { - context.Console.Error.WriteLine(ex.ToString()); - } - else - { - context.Console.Error.WriteLine(ex.Message); - } - Trace.TraceError(ex.ToString()); - } - private static string BuildOptionAlias(string parameterName) { if (string.IsNullOrWhiteSpace(parameterName)) { @@ -319,7 +321,7 @@ Task ICommandHandler.InvokeAsync(InvocationContext context) /// internal bool IsValidPlatform(ITarget target) { - if ((_commandAttribute.Platform & CommandPlatform.Global) != 0) + if ((_commandAttribute.Flags & CommandFlags.Global) != 0) { return true; } @@ -327,15 +329,15 @@ internal bool IsValidPlatform(ITarget target) { if (target.OperatingSystem == OSPlatform.Windows) { - return (_commandAttribute.Platform & CommandPlatform.Windows) != 0; + return (_commandAttribute.Flags & CommandFlags.Windows) != 0; } if (target.OperatingSystem == OSPlatform.Linux) { - return (_commandAttribute.Platform & CommandPlatform.Linux) != 0; + return (_commandAttribute.Flags & CommandFlags.Linux) != 0; } if (target.OperatingSystem == OSPlatform.OSX) { - return (_commandAttribute.Platform & CommandPlatform.OSX) != 0; + return (_commandAttribute.Flags & CommandFlags.OSX) != 0; } } return false; @@ -372,9 +374,7 @@ private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser par { object instance = _factory(services); SetProperties(context, parser, services, instance); - - object[] arguments = BuildArguments(methodInfo, services); - methodInfo.Invoke(instance, arguments); + Utilities.Invoke(methodInfo, instance, services, optional: true); } private void SetProperties(InvocationContext context, Parser parser, IServiceProvider services, object instance) @@ -461,21 +461,6 @@ private void SetProperties(InvocationContext context, Parser parser, IServicePro argument.Property.SetValue(instance, array != null ? array.ToArray() : value); } } - - 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++) - { - 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] = services.GetService(parameterType); - } - return arguments; - } } /// diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs index d1d71c81e1..0876574724 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Reflection.PortableExecutable; namespace Microsoft.Diagnostics.DebugServices.Implementation @@ -107,5 +108,65 @@ public static Stream TryOpenFile(string path) return null; } + + /// + /// Call the constructor of the type and return the instance binding any + /// services in the constructor parameters. + /// + /// type to create + /// services + /// if true, the service is not required + /// type instance + public static object InvokeConstructor(Type type, IServiceProvider provider, bool optional) + { + ConstructorInfo constructor = type.GetConstructors().Single(); + object[] arguments = BuildArguments(constructor, provider, optional); + try + { + return constructor.Invoke(arguments); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } + + /// + /// Call the method and bind any services in the constructor parameters. + /// + /// method to invoke + /// class instance or null if static + /// services + /// if true, the service is not required + /// method return value + public static object Invoke(MethodBase method, object instance, IServiceProvider provider, bool optional) + { + object[] arguments = BuildArguments(method, provider, optional); + try + { + return method.Invoke(instance, arguments); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } + + private static object[] BuildArguments(MethodBase methodBase, IServiceProvider services, bool optional) + { + ParameterInfo[] parameters = methodBase.GetParameters(); + object[] arguments = new object[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) + { + // The parameter will passed as null to allow for "optional" services. The invoked + // method needs to check for possible null parameters. + arguments[i] = services.GetService(parameters[i].ParameterType); + if (arguments[i] is null && !optional) + { + throw new DiagnosticsException($"The {parameters[i].ParameterType} service is required by the {parameters[i].Name} parameter"); + } + } + return arguments; + } } } diff --git a/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs b/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs index a02161eb70..a41d076712 100644 --- a/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs +++ b/src/Microsoft.Diagnostics.DebugServices/CommandAttributes.cs @@ -7,10 +7,10 @@ namespace Microsoft.Diagnostics.DebugServices { /// - /// OS Platforms to add command + /// Command flags to filter by OS Platforms, control scope and how the command is registered. /// [Flags] - public enum CommandPlatform : byte + public enum CommandFlags : byte { Windows = 0x01, Linux = 0x02, @@ -21,6 +21,11 @@ public enum CommandPlatform : byte /// Global = 0x08, + /// + /// Command is not added through reflection, but manually with command service API. + /// + Manual = 0x10, + /// /// Default. All operating system, but target is required /// @@ -49,9 +54,9 @@ public class CommandAttribute : Attribute public string[] Aliases = Array.Empty(); /// - /// Optional OS platform for the command + /// Command flags to filter by OS Platforms, control scope and how the command is registered. /// - public CommandPlatform Platform = CommandPlatform.Default; + public CommandFlags Flags = CommandFlags.Default; /// /// A string of options that are parsed before the command line options diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs index b5d2c54556..8f1bb430e7 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpAsyncCommand.cs @@ -13,7 +13,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = CommandName, Help = "Displays information about async \"stacks\" on the garbage-collected heap.")] + [Command(Name = CommandName, Aliases = new string[] { "DumpAsync" }, Help = "Displays information about async \"stacks\" on the garbage-collected heap.")] public sealed class DumpAsyncCommand : ExtensionCommandBase { /// The name of the command. diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs index 10ec2d68d1..974a796816 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs @@ -8,7 +8,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands { - [Command(Name = "logging", Help = "Enable/disable internal logging", Platform = CommandPlatform.Global)] + [Command(Name = "logging", Help = "Enable/disable internal logging", Flags = CommandFlags.Global)] public class LoggingCommand : CommandBase { [Option(Name = "enable", Help = "Enable internal logging.")] diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs index 756b302c00..eaf0797af8 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetSymbolServerCommand.cs @@ -10,7 +10,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands Name = "setsymbolserver", Aliases = new string[] { "SetSymbolServer" }, Help = "Enable and set symbol server support for symbols and module download", - Platform = CommandPlatform.Global)] + Flags = CommandFlags.Global)] + [Command( + Name = "loadsymbols", + DefaultOptions = "--loadsymbols", + Help = "Load symbols for all modules", + Flags = CommandFlags.Global)] public class SetSymbolServerCommand : CommandBase { public ISymbolService SymbolService { get; set; } diff --git a/src/Microsoft.Diagnostics.Repl/ExitCommand.cs b/src/Microsoft.Diagnostics.Repl/ExitCommand.cs index c7678c3f1f..4d4eb4eee1 100644 --- a/src/Microsoft.Diagnostics.Repl/ExitCommand.cs +++ b/src/Microsoft.Diagnostics.Repl/ExitCommand.cs @@ -7,7 +7,7 @@ namespace Microsoft.Diagnostics.Repl { - [Command(Name = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exit interactive mode.", Platform = CommandPlatform.Global)] + [Command(Name = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exit interactive mode.", Flags = CommandFlags.Global | CommandFlags.Manual)] public class ExitCommand : CommandBase { private readonly Action _exit; diff --git a/src/Microsoft.Diagnostics.Repl/HelpCommand.cs b/src/Microsoft.Diagnostics.Repl/HelpCommand.cs index cbf3699f4f..b4153706f4 100644 --- a/src/Microsoft.Diagnostics.Repl/HelpCommand.cs +++ b/src/Microsoft.Diagnostics.Repl/HelpCommand.cs @@ -7,7 +7,7 @@ namespace Microsoft.Diagnostics.Repl { - [Command(Name = "help", Help = "Display help for a command.", Platform = CommandPlatform.Global)] + [Command(Name = "help", Help = "Display help for a command.", Flags = CommandFlags.Global | CommandFlags.Manual)] public class HelpCommand : CommandBase { [Argument(Help = "Command to find help.")] diff --git a/src/SOS/SOS.Extensions/HostServices.cs b/src/SOS/SOS.Extensions/HostServices.cs index 41b9947ac8..91308b8945 100644 --- a/src/SOS/SOS.Extensions/HostServices.cs +++ b/src/SOS/SOS.Extensions/HostServices.cs @@ -13,6 +13,7 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; namespace SOS.Extensions { @@ -128,7 +129,7 @@ private HostServices() builder.AddMethod(new FlushTargetDelegate(FlushTarget)); builder.AddMethod(new DestroyTargetDelegate(DestroyTarget)); builder.AddMethod(new DispatchCommandDelegate(DispatchCommand)); - builder.AddMethod(new DispatchCommandDelegate(DisplayHelp)); + builder.AddMethod(new DisplayHelpDelegate(DisplayHelp)); builder.AddMethod(new UninitializeDelegate(Uninitialize)); IHostServices = builder.Complete(); @@ -325,15 +326,30 @@ private void DestroyTarget( private HResult DispatchCommand( IntPtr self, - string commandLine) + string commandName, + string commandArguments) { - if (commandLine == null) + if (string.IsNullOrWhiteSpace(commandName)) { return HResult.E_INVALIDARG; } + if (!_commandService.IsCommand(commandName)) + { + return HResult.E_NOTIMPL; + } try { - return _commandService.Execute(commandLine, _contextService.Services); + StringBuilder sb = new(); + sb.Append(commandName); + if (!string.IsNullOrWhiteSpace(commandArguments)) + { + sb.Append(' '); + sb.Append(commandArguments); + } + if (_commandService.Execute(sb.ToString(), _contextService.Services)) + { + return HResult.S_OK; + } } catch (Exception ex) { @@ -344,11 +360,11 @@ private HResult DispatchCommand( private HResult DisplayHelp( IntPtr self, - string command) + string commandName) { try { - if (!_commandService.DisplayHelp(command, _contextService.Services)) + if (!_commandService.DisplayHelp(commandName, _contextService.Services)) { return HResult.E_INVALIDARG; } @@ -424,12 +440,13 @@ private delegate void DestroyTargetDelegate( [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate HResult DispatchCommandDelegate( [In] IntPtr self, - [In, MarshalAs(UnmanagedType.LPStr)] string commandLine); + [In, MarshalAs(UnmanagedType.LPStr)] string commandName, + [In, MarshalAs(UnmanagedType.LPStr)] string commandArguments); [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate HResult DisplayHelpDelegate( [In] IntPtr self, - [In, MarshalAs(UnmanagedType.LPStr)] string command); + [In, MarshalAs(UnmanagedType.LPStr)] string commandName); [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate void UninitializeDelegate( diff --git a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs index c37b4f7b1e..d4e5f38570 100644 --- a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs +++ b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs @@ -60,13 +60,13 @@ namespace SOS.Hosting [Command(Name = "threadpool", DefaultOptions = "ThreadPool", Help = "Lists basic information about the thread pool.")] [Command(Name = "verifyheap", DefaultOptions = "VerifyHeap", Help = "Checks the GC heap for signs of corruption.")] [Command(Name = "verifyobj", DefaultOptions = "VerifyObj", Help = "Checks the object for signs of corruption.")] - [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 = "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 = "gchandleleaks", DefaultOptions = "GCHandleLeaks", Platform = CommandPlatform.Windows, Help = "Helps in tracking down GCHandle leaks")] + [Command(Name = "comstate", DefaultOptions = "COMState", Flags = CommandFlags.Windows, Help = "Lists the COM apartment model for each thread.")] + [Command(Name = "dumprcw", DefaultOptions = "DumpRCW", Flags = CommandFlags.Windows, Help = "Displays information about a Runtime Callable Wrapper.")] + [Command(Name = "dumpccw", DefaultOptions = "DumpCCW", Flags = CommandFlags.Windows, Help = "Displays information about a COM Callable Wrapper.")] + [Command(Name = "dumppermissionset",DefaultOptions = "DumpPermissionSet", Flags = CommandFlags.Windows, Help = "Displays a PermissionSet object (debug build only).")] + [Command(Name = "gchandleleaks", DefaultOptions = "GCHandleLeaks", Flags = CommandFlags.Windows, Help = "Helps in tracking down GCHandle leaks")] + [Command(Name = "traverseheap", DefaultOptions = "TraverseHeap", Flags = CommandFlags.Windows, Help = "Writes out a file in a format understood by the CLR Profiler.")] + [Command(Name = "watsonbuckets", DefaultOptions = "WatsonBuckets", Flags = CommandFlags.Windows, Help = "Displays the Watson buckets.")] public class SOSCommand : CommandBase { [Argument(Name = "arguments", Help = "Arguments to SOS command.")] @@ -78,7 +78,7 @@ public override void Invoke() { try { Debug.Assert(Arguments != null && Arguments.Length > 0); - string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")); + string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim(); SOSHost.ExecuteCommand(Arguments[0], arguments); } catch (Exception ex) when (ex is FileNotFoundException || ex is EntryPointNotFoundException || ex is InvalidOperationException) { diff --git a/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs b/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs index 0954470bc5..058cdb4dfb 100644 --- a/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs +++ b/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs @@ -125,13 +125,17 @@ private unsafe int ReadVirtual( uint bytesRequested, uint* pbytesRead) { - address &= _ignoreAddressBitsMask; - if (!_memoryService.ReadMemory(address, buffer, unchecked((int)bytesRequested), out int bytesRead)) + int read = 0; + if (bytesRequested > 0) { - Trace.TraceError("CorDebugDataTargetWrappter.ReadVirtual FAILED address {0:X16} size {1:X8}", address, bytesRequested); - return HResult.E_FAIL; + address &= _ignoreAddressBitsMask; + if (!_memoryService.ReadMemory(address, buffer, unchecked((int)bytesRequested), out read)) + { + Trace.TraceError("CorDebugDataTargetWrappter.ReadVirtual FAILED address {0:X16} size {1:X8}", address, bytesRequested); + return HResult.E_FAIL; + } } - SOSHost.Write(pbytesRead, (uint)bytesRead); + SOSHost.Write(pbytesRead, (uint)read); return HResult.S_OK; } diff --git a/src/SOS/SOS.Hosting/DataTargetWrapper.cs b/src/SOS/SOS.Hosting/DataTargetWrapper.cs index 826143d224..a5a9d4992e 100644 --- a/src/SOS/SOS.Hosting/DataTargetWrapper.cs +++ b/src/SOS/SOS.Hosting/DataTargetWrapper.cs @@ -149,17 +149,21 @@ private int ReadVirtual( ulong address, IntPtr buffer, uint bytesRequested, - uint* bytesRead) + uint* pbytesRead) { Debug.Assert(address != MagicCallbackConstant); - address &= _ignoreAddressBitsMask; - if (!_memoryService.ReadMemory(address, buffer, unchecked((int)bytesRequested), out int read)) + int read = 0; + if (bytesRequested > 0) { - Trace.TraceError("DataTargetWrapper.ReadVirtual FAILED address {0:X16} size {1:X8}", address, bytesRequested); - SOSHost.Write(bytesRead); - return HResult.E_FAIL; + address &= _ignoreAddressBitsMask; + if (!_memoryService.ReadMemory(address, buffer, unchecked((int)bytesRequested), out read)) + { + Trace.TraceError("DataTargetWrapper.ReadVirtual FAILED address {0:X16} size {1:X8}", address, bytesRequested); + SOSHost.Write(pbytesRead); + return HResult.E_FAIL; + } } - SOSHost.Write(bytesRead, (uint)read); + SOSHost.Write(pbytesRead, (uint)read); return HResult.S_OK; } diff --git a/src/SOS/SOS.Hosting/SOSLibrary.cs b/src/SOS/SOS.Hosting/SOSLibrary.cs index 1009e5a7be..a43683f7c3 100644 --- a/src/SOS/SOS.Hosting/SOSLibrary.cs +++ b/src/SOS/SOS.Hosting/SOSLibrary.cs @@ -159,7 +159,7 @@ public void ExecuteCommand(IntPtr client, string command, string arguments) var commandFunc = SOSHost.GetDelegateFunction(_sosLibrary, command); if (commandFunc == null) { - throw new EntryPointNotFoundException($"Can not find SOS command: {command}"); + throw new DiagnosticsException($"SOS command not found: {command}"); } int result = commandFunc(client, arguments ?? ""); if (result != HResult.S_OK) diff --git a/src/SOS/SOS.UnitTests/SOSRunner.cs b/src/SOS/SOS.UnitTests/SOSRunner.cs index 2b6c697e17..616733bfae 100644 --- a/src/SOS/SOS.UnitTests/SOSRunner.cs +++ b/src/SOS/SOS.UnitTests/SOSRunner.cs @@ -924,6 +924,8 @@ public async Task LoadSosExtension() { commands.Add($"sethostruntime {setHostRuntime}"); } + // Disabled until https://github.com/dotnet/diagnostics/issues/3265 is fixed. +#if DISABLED // If a single-file app, add the path to runtime so SOS can find DAC/DBI locally. if (_config.PublishSingleFile) { @@ -932,6 +934,7 @@ public async Task LoadSosExtension() commands.Add($"setclrpath {runtimeSymbolsPath}"); } } +#endif if (!isHostRuntimeNone && !string.IsNullOrEmpty(setSymbolServer)) { commands.Add($"setsymbolserver {setSymbolServer}"); @@ -1015,7 +1018,7 @@ public async Task RunSosCommand(string command, bool extensionCommand = fa case NativeDebugger.Cdb: if (extensionCommand) { - command = "!ext " + command; + command = "!sos " + command; } else { @@ -1023,20 +1026,26 @@ public async Task RunSosCommand(string command, bool extensionCommand = fa } break; case NativeDebugger.Lldb: - if (!extensionCommand) - { - command = "sos " + command; - } + command = "sos " + command; break; case NativeDebugger.DotNetDump: - int index = command.IndexOf(' '); - if (index != -1) { - // lowercase just the command name not the rest of the command line - command = command.Substring(0, index).ToLowerInvariant() + command.Substring(index); + if (extensionCommand) + { + command = "sos " + command; } - else { - // it is only the command name - command = command.ToLowerInvariant(); + else + { + int index = command.IndexOf(' '); + if (index != -1) + { + // lowercase just the command name not the rest of the command line + command = command.Substring(0, index).ToLowerInvariant() + command.Substring(index); + } + else + { + // it is only the command name + command = command.ToLowerInvariant(); + } } break; default: diff --git a/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script b/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script index 76470093ed..32fb62b9eb 100644 --- a/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script +++ b/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script @@ -68,9 +68,11 @@ VERIFY:\s*\s+0x\s+\(\)\s+ EXTCOMMAND:registers VERIFY:\s*([r|e]ip|pc) = 0x\s+ -SOSCOMMAND:ThreadPool +EXTCOMMAND:ClrStack -SOSCOMMAND:VerifyHeap +EXTCOMMAND:ThreadPool + +EXTCOMMAND:VerifyHeap SOSCOMMAND:DumpHeap VERIFY:\s+Address\s+MT\s+Size\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script index e4afc5c497..3cf92dd763 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script @@ -197,7 +197,6 @@ VERIFY:.*\s+\s+\s+System\.String\[\].* ENDIF:NETCORE_OR_DOTNETDUMP # Issue: https://github.com/dotnet/diagnostics/issues/2947 -!IFDEF:MAJOR_RUNTIME_VERSION_GE_7 !IFDEF:DOTNETDUMP !IFDEF:ARM @@ -220,7 +219,6 @@ VERIFY:(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+ ENDIF:ARM ENDIF:DOTNETDUMP -ENDIF:MAJOR_RUNTIME_VERSION_GE_7 # Verify that IP2MD works (uses IP from ClrStack) SOSCOMMAND:ClrStack diff --git a/src/SOS/SOS.UnitTests/Scripts/StackTests.script b/src/SOS/SOS.UnitTests/Scripts/StackTests.script index e7bb519a48..d140295527 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackTests.script @@ -151,7 +151,6 @@ VERIFY:.*\s+\s+\s+System\.InvalidOperationException\s+ VERIFY:.*\s+\s+\s+System\.String.* # Issue: https://github.com/dotnet/diagnostics/issues/2947 -!IFDEF:MAJOR_RUNTIME_VERSION_GE_7 !IFDEF:DOTNETDUMP !IFDEF:ARM @@ -174,4 +173,3 @@ VERIFY:.*\s+\s+\s+\(MethodDesc\s+\s+\+\s*0x\s+Ne ENDIF:ARM ENDIF:DOTNETDUMP -ENDIF:MAJOR_RUNTIME_VERSION_GE_7 diff --git a/src/SOS/SOS.UnitTests/Scripts/WebApp.script b/src/SOS/SOS.UnitTests/Scripts/WebApp.script index f5e7e5eef7..ffa91ff7e9 100644 --- a/src/SOS/SOS.UnitTests/Scripts/WebApp.script +++ b/src/SOS/SOS.UnitTests/Scripts/WebApp.script @@ -34,11 +34,19 @@ EXTCOMMAND:parallelstacks EXTCOMMAND:timerinfo VERIFY:\s*\s*timers\s* +SOSCOMMAND:runtimes + +EXTCOMMAND:runtimes + # Verify that ClrStack with no options works SOSCOMMAND:ClrStack VERIFY:.*OS Thread Id:\s+0x\s+.* VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+ +EXTCOMMAND:ClrStack +VERIFY:.*OS Thread Id:\s+0x\s+.* +VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+ + # Verify that ClrStack for all threads works SOSCOMMAND:ClrStack -all diff --git a/src/SOS/Strike/Strike.vcxproj b/src/SOS/Strike/Strike.vcxproj index b8ec9fe5bc..be63febcfb 100644 --- a/src/SOS/Strike/Strike.vcxproj +++ b/src/SOS/Strike/Strike.vcxproj @@ -265,7 +265,7 @@ true Level3 true - NDEBUG;URTBLDENV_FRIENDLY=Retail;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;WIN32;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;_TARGET_WIN64_=1;SOS_TARGET_AMD64=1;SOS_TARGET_ARM64=1;STRIKE;USE_STL;FX_VER_INTERNALNAME_STR=SOS.dll;CMAKE_INTDIR="Release";sos_EXPORTS;%(PreprocessorDefinitions) + HOST_WINDOWS;NDEBUG;URTBLDENV_FRIENDLY=Retail;_AMD64_;_WIN64;AMD64;BIT64=1;_TARGET_64BIT_=1;_TARGET_AMD64_=1;DBG_TARGET_64BIT=1;DBG_TARGET_AMD64=1;DBG_TARGET_WIN64=1;WIN32;_WIN32;WINVER=0x0602;_WIN32_WINNT=0x0602;WIN32_LEAN_AND_MEAN=1;_CRT_SECURE_NO_WARNINGS;FEATURE_COMINTEROP;FEATURE_HIJACK;_SECURE_SCL=0;_TARGET_WIN64_=1;SOS_TARGET_AMD64=1;SOS_TARGET_ARM64=1;STRIKE;USE_STL;FX_VER_INTERNALNAME_STR=SOS.dll;CMAKE_INTDIR="Release";sos_EXPORTS;%(PreprocessorDefinitions) $(IntDir) diff --git a/src/SOS/Strike/sos.def b/src/SOS/Strike/sos.def index e548eac314..6dec87fd17 100644 --- a/src/SOS/Strike/sos.def +++ b/src/SOS/Strike/sos.def @@ -74,7 +74,9 @@ EXPORTS EHInfo ehinfo=EHInfo Ehinfo=EHInfo + enummemory ext + sos=ext FinalizeQueue finalizequeue=FinalizeQueue fq=FinalizeQueue diff --git a/src/SOS/Strike/sos_unixexports.src b/src/SOS/Strike/sos_unixexports.src index 35bd51fa0c..9ac9f71d9a 100644 --- a/src/SOS/Strike/sos_unixexports.src +++ b/src/SOS/Strike/sos_unixexports.src @@ -4,13 +4,11 @@ AnalyzeOOM bpmd -clrmodules ClrStack dbgout DumpALC DumpArray DumpAssembly -DumpAsync DumpClass DumpDelegate DumpDomain @@ -32,7 +30,7 @@ EEHeap EEVersion EEStack EHInfo -ext +enummemory FinalizeQueue FindAppDomain FindRoots @@ -50,7 +48,6 @@ HistRoot HistStats IP2MD ListNearObj -logging Name2EE ObjSize PrintException @@ -58,9 +55,9 @@ PathTo runtimes StopOnCatch SetClrPath -SetSymbolServer SOSFlush SOSStatus +runtimes SuppressJitOptimization SyncBlk Threads diff --git a/src/SOS/Strike/sosdocsunix.txt b/src/SOS/Strike/sosdocsunix.txt index 6eb004882b..00127b9d5a 100644 --- a/src/SOS/Strike/sosdocsunix.txt +++ b/src/SOS/Strike/sosdocsunix.txt @@ -2227,8 +2227,7 @@ You can use the "dotnet --info" in a command shell to find the path of an instal COMMAND: setsymbolserver. COMMAND: loadsymbols. -COMMAND: sympath. -SetSymbolServer [-ms] [-disable] [-log] [-loadsymbols] [-cache ] [-directory ] [-timeout ] [-pat ] [-sympath ] [] +SetSymbolServer [-ms] [-disable] [-log] [-loadsymbols] [-cache ] [-directory ] [-timeout ] [-pat ] [] -ms - Use the public Microsoft symbol server. -disable - Disable symbol download support. @@ -2236,7 +2235,6 @@ SetSymbolServer [-ms] [-disable] [-log] [-loadsymbols] [-cache ] [- -timeout - Specify the symbol server timeout in minutes -pat - Access token to the authenticated server. -cache - Specific a symbol cache directory. The default is $HOME/.dotnet/symbolcache if not specified. --sympath - Add server, cache and directory paths in the Windows symbol path format. -loadsymbols - Attempts to download the native .NET Core symbols for the runtime - Symbol server URL. @@ -2260,10 +2258,6 @@ To add a directory to search for symbols: This command can be used so the module/symbol file structure does not have to match the machine file structure that the core dump was generated. -The "sympath" option/command alias allows Windows symbol paths to be parsed: - - (lldb) sympath "/home/mikem/localsymbols;srv*/home/mikem/symbolcache*https://msdl.microsoft.com/download/symbols". - To clear the default cache run "rm -r $HOME/.dotnet/symbolcache" in a command shell. If you receive an error like the one below on a core dump, you need to set the .NET Core diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index dd5177e95b..5127bc2af1 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -105,6 +105,7 @@ #include "cordebug.h" #include "dacprivate.h" #include "corexcep.h" +#include #define CORHANDLE_MASK 0x1 #define SWITCHED_OUT_FIBER_OSID 0xbaadf00d; @@ -10357,9 +10358,7 @@ DECLARE_API(SOSStatus) IHostServices* hostServices = GetHostServices(); if (hostServices != nullptr) { - std::string command("sosstatus "); - command.append(args); - Status = hostServices->DispatchCommand(command.c_str()); + Status = hostServices->DispatchCommand("sosstatus", args); } else { @@ -15697,6 +15696,95 @@ DECLARE_API(StopOnCatch) return S_OK; } +class EnumMemoryCallback : public ICLRDataEnumMemoryRegionsCallback +{ +private: + LONG m_ref; + bool m_log; + +public: + EnumMemoryCallback(bool log) : + m_ref(1), + m_log(log) + { + } + + virtual ~EnumMemoryCallback() + { + } + + STDMETHODIMP QueryInterface( + ___in REFIID InterfaceId, + ___out PVOID* Interface) + { + if (InterfaceId == IID_IUnknown || + InterfaceId == IID_ICLRDataEnumMemoryRegionsCallback) + { + *Interface = (ICLRDataEnumMemoryRegionsCallback*)this; + AddRef(); + return S_OK; + } + else + { + *Interface = nullptr; + return E_NOINTERFACE; + } + } + + STDMETHODIMP_(ULONG) AddRef() + { + LONG ref = InterlockedIncrement(&m_ref); + return ref; + } + + STDMETHODIMP_(ULONG) Release() + { + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; + } + + HRESULT STDMETHODCALLTYPE EnumMemoryRegion( + /* [in] */ CLRDATA_ADDRESS address, + /* [in] */ ULONG32 size) + { + if (m_log) + { + ExtOut("%016llx %08x\n", address, size); + } + return S_OK; + } +}; + +DECLARE_API(enummemory) +{ + INIT_API(); + + ToRelease enumMemoryRegions; + Status = g_clrData->QueryInterface(__uuidof(ICLRDataEnumMemoryRegions), (void**)&enumMemoryRegions); + if (SUCCEEDED(Status)) + { + ToRelease callback = new EnumMemoryCallback(false); + ULONG32 minidumpType = + (MiniDumpWithPrivateReadWriteMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithTokenInformation); + Status = enumMemoryRegions->EnumMemoryRegions(callback, minidumpType, CLRDataEnumMemoryFlags::CLRDATA_ENUM_MEM_DEFAULT); + if (FAILED(Status)) + { + ExtErr("EnumMemoryRegions FAILED %08x\n", Status); + } + } + return Status; +} + #ifndef FEATURE_PAL // This is an undocumented SOS extension command intended to help test SOS @@ -16008,9 +16096,7 @@ DECLARE_API(SetClrPath) IHostServices* hostServices = GetHostServices(); if (hostServices != nullptr) { - std::string command("setclrpath "); - command.append(args); - return hostServices->DispatchCommand(command.c_str()); + return hostServices->DispatchCommand("setclrpath", args); } else { @@ -16054,9 +16140,7 @@ DECLARE_API(runtimes) IHostServices* hostServices = GetHostServices(); if (hostServices != nullptr) { - std::string command("runtimes "); - command.append(args); - Status = hostServices->DispatchCommand(command.c_str()); + Status = hostServices->DispatchCommand("runtimes", args); } else { @@ -16099,31 +16183,22 @@ DECLARE_API(runtimes) return Status; } +#ifdef HOST_WINDOWS + // // Executes managed extension commands // -HRESULT ExecuteCommand(PCSTR command, PCSTR args) +HRESULT ExecuteCommand(PCSTR commandName, PCSTR args) { IHostServices* hostServices = GetHostServices(); if (hostServices != nullptr) { - std::string commandLine(command); - if (args != nullptr && strlen(args) > 0) - { - commandLine.append(" "); - commandLine.append(args); - } - if (!commandLine.empty()) + if (commandName != nullptr && strlen(commandName) > 0) { - return hostServices->DispatchCommand(commandLine.c_str()); + return hostServices->DispatchCommand(commandName, args); } } - else - { - ExtErr("Command not loaded\n"); - return E_FAIL; - } - return S_OK; + return E_NOTIMPL; } // @@ -16162,15 +16237,44 @@ DECLARE_API(logging) return ExecuteCommand("logging", args); } +typedef HRESULT (*PFN_COMMAND)(PDEBUG_CLIENT client, PCSTR args); + // // Executes managed extension commands // DECLARE_API(ext) { INIT_API_EXT(); - return ExecuteCommand("", args); + + if (args == nullptr || strlen(args) <= 0) + { + args = "Help"; + } + std::string arguments(args); + size_t pos = arguments.find(' '); + std::string commandName = arguments.substr(0, pos); + if (pos != std::string::npos) + { + arguments = arguments.substr(pos + 1); + } + else + { + arguments.clear(); + } + Status = ExecuteCommand(commandName.c_str(), arguments.c_str()); + if (Status == E_NOTIMPL) + { + PFN_COMMAND commandFunc = (PFN_COMMAND)GetProcAddress(g_hInstance, commandName.c_str()); + if (commandFunc != nullptr) + { + Status = (*commandFunc)(client, arguments.c_str()); + } + } + return Status; } +#endif // HOST_WINDOWS + void PrintHelp (__in_z LPCSTR pszCmdName) { static LPSTR pText = NULL; diff --git a/src/SOS/inc/hostservices.h b/src/SOS/inc/hostservices.h index 6b7c19eb6f..bf4403845f 100644 --- a/src/SOS/inc/hostservices.h +++ b/src/SOS/inc/hostservices.h @@ -76,18 +76,20 @@ IHostServices : public IUnknown /// /// Dispatches the command line to managed extension /// - /// full command line + /// command name + /// command arguments /// error code virtual HRESULT STDMETHODCALLTYPE DispatchCommand( - PCSTR commandLine) = 0; + PCSTR commandName, + PCSTR arguments) = 0; /// /// Displays the help for a managed extension command /// - /// + /// /// error code virtual HRESULT STDMETHODCALLTYPE DisplayHelp( - PCSTR command) = 0; + PCSTR commandName) = 0; /// /// Uninitialize the extension infrastructure diff --git a/src/SOS/lldbplugin/services.cpp b/src/SOS/lldbplugin/services.cpp index c278558289..fde35e73f8 100644 --- a/src/SOS/lldbplugin/services.cpp +++ b/src/SOS/lldbplugin/services.cpp @@ -2113,19 +2113,17 @@ class ExtensionCommand : public lldb::SBCommandPluginInterface result.SetStatus(lldb::eReturnStatusFailed); return false; } - std::string commandLine; - commandLine.append(m_commandName); - commandLine.append(" "); + std::string commandArguments; if (arguments != nullptr) { - for (const char* arg = *arguments; arg; arg = *(++arguments)) + for (const char* arg = *arguments; arg != nullptr; arg = *(++arguments)) { - commandLine.append(arg); - commandLine.append(" "); + commandArguments.append(arg); + commandArguments.append(" "); } } g_services->FlushCheck(); - HRESULT hr = hostservices->DispatchCommand(commandLine.c_str()); + HRESULT hr = hostservices->DispatchCommand(m_commandName, commandArguments.c_str()); if (hr != S_OK) { result.SetStatus(lldb::eReturnStatusFailed); @@ -2784,6 +2782,66 @@ LLDBServices::AddCommand( return command; } +void +LLDBServices::AddManagedCommand( + const char* name, + const char* help) +{ + HRESULT hr = AddCommand(name, help, nullptr, 0); + if (FAILED(hr)) + { + Output(DEBUG_OUTPUT_ERROR, "AddManagedCommand FAILED %08x\n", hr); + } +} + +bool +LLDBServices::ExecuteCommand( + const char* commandName, + char** arguments, + lldb::SBCommandReturnObject &result) +{ + // Build all the possible arguments into a string + std::string commandArguments; + for (const char* arg = *arguments; arg != nullptr; arg = *(++arguments)) + { + commandArguments.append(arg); + commandArguments.append(" "); + } + // Load and initialize the managed extensions and commands before we check the m_commands list. + IHostServices* hostservices = GetHostServices(); + + // If the command is a native SOS or managed extension command execute it through the lldb command added. + if (m_commands.find(commandName) != m_commands.end()) + { + std::string commandLine; + commandLine.append(commandName); + if (!commandArguments.empty()) + { + commandLine.append(" "); + commandLine.append(commandArguments); + } + lldb::ReturnStatus status = m_interpreter.HandleCommand(commandLine.c_str(), result); + result.SetStatus(status); + return true; + } + + // Fallback to dispatch it as a managed command for those commands that couldn't be added + // directly to the lldb interpreter because of existing commands or aliases. + if (hostservices != nullptr) + { + g_services->FlushCheck(); + HRESULT hr = hostservices->DispatchCommand(commandName, commandArguments.c_str()); + if (hr != E_NOTIMPL) + { + result.SetStatus(hr == S_OK ? lldb::eReturnStatusSuccessFinishResult : lldb::eReturnStatusFailed); + return true; + } + } + + // Command not found; attempt dispatch to native SOS module + return false; +} + HRESULT LLDBServices::InternalOutputVaList( ULONG mask, diff --git a/src/SOS/lldbplugin/services.h b/src/SOS/lldbplugin/services.h index 558f30d856..d741ed51eb 100644 --- a/src/SOS/lldbplugin/services.h +++ b/src/SOS/lldbplugin/services.h @@ -414,5 +414,9 @@ class LLDBServices : public ILLDBServices, public ILLDBServices2, public IDebugg lldb::SBCommand AddCommand(const char *name, lldb::SBCommandPluginInterface *impl, const char *help); + void AddManagedCommand(const char* name, const char* help); + + bool ExecuteCommand( const char* commandName, char** arguments, lldb::SBCommandReturnObject &result); + HRESULT InternalOutputVaList(ULONG mask, PCSTR format, va_list args); }; diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 2ed9a26cf1..d9bee0214f 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -38,21 +38,27 @@ class sosCommand : public lldb::SBCommandPluginInterface { result.SetStatus(lldb::eReturnStatusSuccessFinishResult); - LoadSos(); - - if (g_sosHandle != nullptr) + const char* sosCommand = m_command; + if (sosCommand == nullptr) { - const char* sosCommand = m_command; - if (sosCommand == nullptr) + if (arguments == nullptr || *arguments == nullptr) { - if (arguments == nullptr || *arguments == nullptr) { - sosCommand = "Help"; - } - else + sosCommand = "Help"; + } + else + { + sosCommand = *arguments++; + if (g_services->ExecuteCommand(sosCommand, arguments, result)) { - sosCommand = *arguments++; + return result.Succeeded(); } } + } + + LoadSos(); + + if (g_sosHandle != nullptr) + { CommandFunc commandFunc = (CommandFunc)dlsym(g_sosHandle, sosCommand); if (commandFunc) { @@ -149,16 +155,16 @@ bool sosCommandInitialize(lldb::SBDebugger debugger) { g_services->AddCommand("sos", new sosCommand(nullptr), "Various .NET Core debugging commands. See 'soshelp' for more details. sos "); - g_services->AddCommand("ext", new sosCommand("ext"), "Execute extension command. See 'soshelp' for more details. ext "); + g_services->AddCommand("ext", new sosCommand(nullptr), "Various .NET Core debugging commands. See 'soshelp' for more details. ext "); g_services->AddCommand("bpmd", new sosCommand("bpmd"), "Creates a breakpoint at the specified managed method in the specified module."); - g_services->AddCommand("clrmodules", new sosCommand("clrmodules"), "Lists the managed modules in the process."); + g_services->AddManagedCommand("clrmodules", "Lists the managed modules in the process."); g_services->AddCommand("clrstack", new sosCommand("ClrStack"), "Provides a stack trace of managed code only."); g_services->AddCommand("clrthreads", new sosCommand("Threads"), "List the managed threads running."); g_services->AddCommand("clru", new sosCommand("u"), "Displays an annotated disassembly of a managed method."); g_services->AddCommand("dbgout", new sosCommand("dbgout"), "Enable/disable (-off) internal SOS logging."); g_services->AddCommand("dumpalc", new sosCommand("DumpALC"), "Displays details about a collectible AssemblyLoadContext to which the specified object is loaded."); g_services->AddCommand("dumparray", new sosCommand("DumpArray"), "Displays details about a managed array."); - g_services->AddCommand("dumpasync", new sosCommand("DumpAsync"), "Displays information about async \"stacks\" on the garbage-collected heap."); + g_services->AddManagedCommand("dumpasync", "Displays information about async \"stacks\" on the garbage-collected heap."); g_services->AddCommand("dumpassembly", new sosCommand("DumpAssembly"), "Displays details about an assembly."); g_services->AddCommand("dumpclass", new sosCommand("DumpClass"), "Displays information about a EE class structure at the specified address."); g_services->AddCommand("dumpdelegate", new sosCommand("DumpDelegate"), "Displays information about a delegate."); @@ -198,20 +204,20 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("histstats", new sosCommand("HistStats"), "Displays stress log stats."); g_services->AddCommand("ip2md", new sosCommand("IP2MD"), "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled."); g_services->AddCommand("listnearobj", new sosCommand("ListNearObj"), "displays the object preceeding and succeeding the address passed."); - g_services->AddCommand("loadsymbols", new sosCommand("SetSymbolServer", "-loadsymbols"), "Load the .NET Core native module symbols."); - g_services->AddCommand("logging", new sosCommand("logging"), "Enable/disable internal SOS logging."); + g_services->AddManagedCommand("loadsymbols", "Load the .NET Core native module symbols."); + g_services->AddManagedCommand("logging", "Enable/disable internal SOS logging."); g_services->AddCommand("name2ee", new sosCommand("Name2EE"), "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module."); g_services->AddCommand("objsize", new sosCommand("ObjSize"), "Displays the size of the specified object."); - g_services->AddCommand("pe", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); g_services->AddCommand("pathto", new sosCommand("PathTo"), "Displays the GC path from to ."); + g_services->AddCommand("pe", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); + g_services->AddCommand("printexception", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); g_services->AddCommand("runtimes", new sosCommand("runtimes"), "List the runtimes in the target or change the default runtime."); g_services->AddCommand("stoponcatch", new sosCommand("StopOnCatch"), "Debuggee will break the next time a managed exception is caught during execution"); g_services->AddCommand("setclrpath", new sosCommand("SetClrPath"), "Set the path to load the runtime DAC/DBI files."); - g_services->AddCommand("setsymbolserver", new sosCommand("SetSymbolServer"), "Enables the symbol server support "); - g_services->AddCommand("sympath", new sosCommand("SetSymbolServer", "-sympath"), "Add server, cache and directory paths in the Windows symbol path format."); + g_services->AddManagedCommand("setsymbolserver", "Enables the symbol server support "); g_services->AddCommand("soshelp", new sosCommand("Help"), "Displays all available commands when no parameter is specified, or displays detailed help information about the specified command. soshelp "); - g_services->AddCommand("sosflush", new sosCommand("SOSFlush"), "Flushes the DAC caches."); g_services->AddCommand("sosstatus", new sosCommand("SOSStatus"), "Displays the global SOS status."); + g_services->AddCommand("sosflush", new sosCommand("SOSFlush"), "Flushes the DAC caches."); g_services->AddCommand("syncblk", new sosCommand("SyncBlk"), "Displays the SyncBlock holder info."); g_services->AddCommand("threadpool", new sosCommand("ThreadPool"), "Displays info about the runtime thread pool."); g_services->AddCommand("threadstate", new sosCommand("ThreadState"), "Pretty prints the meaning of a threads state."); diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs index 7532b870df..069e3af3bf 100644 --- a/src/Tools/dotnet-dump/Analyzer.cs +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -58,6 +58,7 @@ public Analyzer() _commandService.AddCommands(new Assembly[] { typeof(SOSHost).Assembly }); _commandService.AddCommands(typeof(HelpCommand), (services) => new HelpCommand(_commandService, services)); _commandService.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleProvider.Stop)); + _commandService.AddCommands(typeof(SOSCommand), (services) => new SOSCommand(_commandService, services)); } public Task Analyze(FileInfo dump_path, string[] command) diff --git a/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs b/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs index a6f7a25c90..6cf6ec614b 100644 --- a/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs +++ b/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs @@ -6,7 +6,7 @@ using System; using System.Text; -namespace Microsoft.Diagnostics.ExtensionCommands +namespace Microsoft.Diagnostics.Tools.Dump { [Command(Name = "readmemory", Aliases = new string[] { "d" }, Help = "Dump memory contents.")] [Command(Name = "db", DefaultOptions = "--ascii:true --unicode:false --ascii-string:false --unicode-string:false -c:128 -l:1 -w:16", Help = "Dump memory as bytes.")] diff --git a/src/Tools/dotnet-dump/Commands/SOSCommand.cs b/src/Tools/dotnet-dump/Commands/SOSCommand.cs new file mode 100644 index 0000000000..4d3a2f4a8f --- /dev/null +++ b/src/Tools/dotnet-dump/Commands/SOSCommand.cs @@ -0,0 +1,60 @@ +// 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.DebugServices.Implementation; +using SOS.Hosting; +using System; +using System.Linq; + +namespace Microsoft.Diagnostics.Tools.Dump +{ + [Command(Name = "sos", Aliases = new string[] { "ext" }, Help = "Run SOS command", Flags = CommandFlags.Global | CommandFlags.Manual)] + public class SOSCommand : CommandBase + { + private readonly CommandService _commandService; + private readonly IServiceProvider _services; + private SOSHost _sosHost; + + [Argument(Name = "arguments", Help = "SOS command and arguments.")] + public string[] Arguments { get; set; } + + public SOSCommand(CommandService commandService, IServiceProvider services) + { + _commandService = commandService; + _services = services; + } + + public override void Invoke() + { + string commandLine; + string commandName; + if (Arguments != null && Arguments.Length > 0) + { + commandLine = string.Concat(Arguments.Select((arg) => arg + " ")).Trim(); + commandName = Arguments[0]; + } + else + { + commandLine = commandName = "help"; + } + if (_commandService.IsCommand(commandName)) + { + _commandService.Execute(commandLine, _services); + } + else + { + if (_sosHost is null) + { + _sosHost = _services.GetService(); + if (_sosHost is null) + { + throw new DiagnosticsException($"'{commandName}' command not found"); + } + } + _sosHost.ExecuteCommand(commandLine); + } + } + } +} diff --git a/src/shared/inc/dumpcommon.h b/src/shared/inc/dumpcommon.h new file mode 100644 index 0000000000..83a0f447c4 --- /dev/null +++ b/src/shared/inc/dumpcommon.h @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef DEBUGGER_DUMPCOMMON_H +#define DEBUGGER_DUMPCOMMON_H + +#ifdef HOST_UNIX +typedef enum _MINIDUMP_TYPE { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000, + MiniDumpWithoutAuxiliaryState = 0x00004000, + MiniDumpWithFullAuxiliaryState = 0x00008000, + MiniDumpWithPrivateWriteCopyMemory = 0x00010000, + MiniDumpIgnoreInaccessibleMemory = 0x00020000, + MiniDumpWithTokenInformation = 0x00040000, + MiniDumpWithModuleHeaders = 0x00080000, + MiniDumpFilterTriage = 0x00100000, + MiniDumpWithAvxXStateContext = 0x00200000, + MiniDumpValidTypeFlags = 0x003fffff, +} MINIDUMP_TYPE; +#endif // HOST_UNIX + +#if defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE) + +// When debugging against minidumps, we frequently need to ignore errors +// due to the dump not having memory content. +// You should be VERY careful using these macros. Because our code does not +// distinguish target types, when you allow memory to be missing because a dump +// target may not have that memory content by-design you are also implicitly +// allowing that same data to be missing from a live debugging target. +// Also, be aware that these macros exist in code under vm\. You must be careful to +// only allow them to change execution for DAC and DBI. +// Be careful state is such that execution can continue if the target is missing +// memory. +// In general, there are two solutions to this problem: +// a) add the memory to all minidumps +// b) stop forcing the memory to always be present +// All decisions between a & b focus on cost. For a, cost is adding the memory & a complete +// path to locate it to the dump, both in terms of dump generation time and most +// especially in terms of dump size (we cannot make MiniDumpNormal many MB for trivial +// apps). +// For b, cost is that we lose some of our validation when we have to turn off asserts +// and other checks for targets that should always have the missing memory present +// because we have no concept of allowing it to be missing only from a dump. + +// This seemingly awkward try block starting tag is so that when the macro is used over +// multiple source lines we don't create a useless try/catch block. This is important +// when using the macros in vm\ code. +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY EX_TRY +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY \ + EX_CATCH \ + { \ + if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) ) \ + { \ + EX_RETHROW; \ + } \ + } \ + EX_END_CATCH(SwallowAllExceptions) + +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER EX_TRY +#define EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + EX_CATCH \ + { \ + if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) ) \ + { \ + EX_RETHROW; \ + } \ + else \ + +#define EX_TRY_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY EX_TRY +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY \ + EX_CATCH \ + { \ + if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_TARGET_INCONSISTENT)) \ + { \ + EX_RETHROW; \ + } \ + } \ + EX_END_CATCH(SwallowAllExceptions) + + +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + } \ + EX_END_CATCH(SwallowAllExceptions) + +// Only use this version for wrapping single source lines, or you'll make debugging +// painful. +#define ALLOW_DATATARGET_MISSING_MEMORY(sourceCode) \ + EX_TRY \ + { \ + sourceCode \ + } \ + EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY + +#define ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY(sourceCode) \ + EX_TRY \ + { \ + sourceCode \ + } \ + EX_END_CATCH_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY + +#else +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + #error This macro is only intended for use in DAC code! +#define EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + #error This macro is only intended for use in DAC code! +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + #error This macro is only intended for use in DAC code! + + +#define ALLOW_DATATARGET_MISSING_MEMORY(sourceCode) \ + sourceCode + +#endif // defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE) + + +#endif //DEBUGGER_DUMPCOMMON_H