Add extension load testing that includes duplicate command names.
Add internal CommandGroup class to CommandService
dotnet-dump analyze -c/--command will exit on any parsing errors or
exceptions in the command
command help not displayed on parsing errors/invalid options
Add INIT_API_CHECK_MANAGED native SOS command macro
Rename clrmodules command to assemblies and keep clrmodules as an alias
Support static HelpInvoke/FilterInvoke methods
Add command service testing
Better command service help interface. Returns help text instead of
printing it on the console directly. ICommandService.DisplayHelp() =>
GetHelpText(). Better help sorting.
Fix some SOS scripts
Return error code for command line option errors in native SOS
Load next to executing assembly first when hosted under desktop
Framework
Test using "clrthreads" instead of "Threads"
Replace testing "u" with "clru"
Add more general command filter mechanism. Add FilterType property to
Command attribute. Remove OS filter command flags.
NativeAddressHelper service work
Move Windows managed command stubs to separate file
Add SOS.Hosting services to SOS.Extensions for better native command
help. This requires the special ManagedOnlyCommandFilter service to
prevent recursion of the C++ commands. On Windows this recursion is from
"C++ command" -> "checking if managed version" -> "executing the command
in SOS.Hosting" -> "C++ command".
Help is now uniform across managed/native, alphabetized and filtered by
the current runtime.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetStack.UnitTests", "src\tests\dotnet-stack\DotnetStack.UnitTests.csproj", "{E8F133F8-4D20-475D-9D16-2BA236DAB65F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Diagnostics.WebSocketServer", "src\Microsoft.Diagnostics.WebSocketServer\Microsoft.Diagnostics.WebSocketServer.csproj", "{1043FA82-37CC-4809-80DC-C1EB06A55133}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.WebSocketServer", "src\Microsoft.Diagnostics.WebSocketServer\Microsoft.Diagnostics.WebSocketServer.csproj", "{1043FA82-37CC-4809-80DC-C1EB06A55133}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestExtension", "src\tests\TestExtension\TestExtension.csproj", "{C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
{1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU
{1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x86.ActiveCfg = Debug|Any CPU
{1043FA82-37CC-4809-80DC-C1EB06A55133}.RelWithDebInfo|x86.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|Any CPU.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|ARM64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x86.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Checked|x86.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x64.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Debug|x86.Build.0 = Debug|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|ARM64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x86.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.Release|x86.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{DFF48CB6-4504-41C6-A8F1-F4A3D316D49F} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{E8F133F8-4D20-475D-9D16-2BA236DAB65F} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
{1043FA82-37CC-4809-80DC-C1EB06A55133} = {19FAB78C-3351-4911-8F0C-8C6056401740}
+ {C6EB3C21-FDFF-4CF0-BE3A-3D1A3924408E} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
string probingPath;
Assembly assembly;
+ // Look next to the executing assembly
+ probingPath = Path.Combine(_defaultAssembliesPath, fileName);
+ Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly");
+ if (Probe(probingPath, referenceName.Version, out assembly))
+ {
+ Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly");
+ return assembly;
+ }
+
// Look next to requesting assembly
assemblyPath = args.RequestingAssembly?.Location;
if (!string.IsNullOrEmpty(assemblyPath))
}
}
- // Look next to the executing assembly
- probingPath = Path.Combine(_defaultAssembliesPath, fileName);
- Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly");
- if (Probe(probingPath, referenceName.Version, out assembly))
- {
- Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly");
- return assembly;
- }
-
return null;
}
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.CommandLine.Parsing;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
-using System.Runtime.InteropServices;
+using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.DebugServices.Implementation
/// </summary>
public class CommandService : ICommandService
{
- private Parser _parser;
- private readonly CommandLineBuilder _rootBuilder;
- private readonly Dictionary<string, CommandHandler> _commandHandlers = new();
+ private readonly List<CommandGroup> _commandGroups = new();
+ private readonly string _commandPrompt;
/// <summary>
/// Create an instance of the command processor;
/// <param name="commandPrompt">command prompted used in help message</param>
public CommandService(string commandPrompt = null)
{
- _rootBuilder = new CommandLineBuilder(new Command(commandPrompt ?? ">"));
- _rootBuilder.UseHelpBuilder((bindingContext) => new LocalHelpBuilder(this, bindingContext.Console, useHelpBuilder: false));
+ _commandPrompt = commandPrompt ?? ">";
+
+ // Create default command group (should always be last in this list)
+ _commandGroups.Add(new CommandGroup(_commandPrompt));
}
/// <summary>
/// </summary>
/// <param name="commandLine">command line text</param>
/// <param name="services">services for the command</param>
- /// <returns>true success, false failure</returns>
+ /// <returns>true - found command, false - command not found</returns>
+ /// <exception cref="ArgumentException">empty command line</exception>
+ /// <exception cref="DiagnosticsException">other errors</exception>
+ /// <exception cref="CommandParsingException ">parsing error</exception>
public bool Execute(string commandLine, IServiceProvider services)
{
- // Parse the command line and invoke the command
- ParseResult parseResult = Parser.Parse(commandLine);
+ string[] commandLineArray = CommandLineStringSplitter.Instance.Split(commandLine).ToArray();
+ if (commandLineArray.Length <= 0)
+ {
+ throw new ArgumentException("Empty command line", nameof(commandLine));
+ }
+ string commandName = commandLineArray[0].Trim();
+ return Execute(commandName, commandLineArray, services);
+ }
+
+ /// <summary>
+ /// Parse and execute the command.
+ /// </summary>
+ /// <param name="commandName">command name</param>
+ /// <param name="commandArguments">command arguments/options</param>
+ /// <param name="services">services for the command</param>
+ /// <returns>true - found command, false - command not found</returns>
+ /// <exception cref="ArgumentException">empty command name or arguments</exception>
+ /// <exception cref="DiagnosticsException">other errors</exception>
+ /// <exception cref="CommandParsingException ">parsing error</exception>
+ public bool Execute(string commandName, string commandArguments, IServiceProvider services)
+ {
+ commandName = commandName.Trim();
+ string[] commandLineArray = CommandLineStringSplitter.Instance.Split(commandName + " " + (commandArguments ?? "")).ToArray();
+ if (commandLineArray.Length <= 0)
+ {
+ throw new ArgumentException("Empty command name or arguments", nameof(commandArguments));
+ }
+ return Execute(commandName, commandLineArray, services);
+ }
- InvocationContext context = new(parseResult, new LocalConsole(services));
- if (parseResult.Errors.Count > 0)
+ /// <summary>
+ /// Find, parse and execute the command.
+ /// </summary>
+ /// <param name="commandName">command name</param>
+ /// <param name="commandLineArray">command line</param>
+ /// <param name="services">services for the command</param>
+ /// <returns>true - found command, false - command not found</returns>
+ /// <exception cref="ArgumentException">empty command name</exception>
+ /// <exception cref="DiagnosticsException">other errors</exception>
+ /// <exception cref="CommandParsingException ">parsing error</exception>
+ private bool Execute(string commandName, string[] commandLineArray, IServiceProvider services)
+ {
+ if (string.IsNullOrEmpty(commandName))
{
- context.InvocationResult = new ParseErrorResult();
+ throw new ArgumentException("Empty command name", nameof(commandName));
}
- else
+ List<string> messages = new();
+ foreach (CommandGroup group in _commandGroups)
{
- if (parseResult.CommandResult.Command is Command command)
+ if (group.TryGetCommandHandler(commandName, out CommandHandler handler))
{
- if (command.Handler is CommandHandler handler)
+ try
{
- ITarget target = services.GetService<ITarget>();
- if (!handler.IsValidPlatform(target))
+ if (handler.IsCommandSupported(group.Parser, services))
{
- if (target != null)
- {
- context.Console.Error.WriteLine($"Command '{command.Name}' not supported on this target");
- }
- else
+ if (group.Execute(commandLineArray, services))
{
- context.Console.Error.WriteLine($"Command '{command.Name}' needs a target");
+ return true;
}
- return false;
- }
- try
- {
- handler.Invoke(context, services);
}
- catch (Exception ex)
+ if (handler.FilterInvokeMessage != null)
{
- if (ex is NullReferenceException or
- ArgumentException or
- ArgumentNullException or
- ArgumentOutOfRangeException or
- NotImplementedException)
- {
- context.Console.Error.WriteLine(ex.ToString());
- }
- else
- {
- context.Console.Error.WriteLine(ex.Message);
- }
- Trace.TraceError(ex.ToString());
- return false;
+ messages.Add(handler.FilterInvokeMessage);
}
}
+ catch (CommandNotFoundException ex)
+ {
+ messages.Add(ex.Message);
+ }
}
}
-
- context.InvocationResult?.Apply(context);
- return context.ResultCode == 0;
+ if (messages.Count > 0)
+ {
+ throw new CommandNotFoundException(string.Concat(messages.Select(s => s + Environment.NewLine)));
+ }
+ return false;
}
/// <summary>
/// Displays the help for a command
/// </summary>
- /// <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)
+ /// <returns>command invocation and help enumeration</returns>
+ public IEnumerable<(string Invocation, string Help)> GetAllCommandHelp(IServiceProvider services)
{
- Command command = null;
- if (!string.IsNullOrEmpty(commandName))
+ List<(string Invocation, string Help)> help = new();
+ foreach (CommandGroup group in _commandGroups)
{
- 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)
+ foreach (CommandHandler handler in group.CommandHandlers)
{
- ITarget target = services.GetService<ITarget>();
- if (!handler.IsValidPlatform(target))
+ try
+ {
+ if (handler.IsCommandSupported(group.Parser, services))
+ {
+ string invocation = handler.HelpInvocation;
+ help.Add((invocation, handler.Help));
+ }
+ }
+ catch (CommandNotFoundException)
{
- return false;
}
}
}
- else
- {
- ITarget target = services.GetService<ITarget>();
+ return help;
+ }
- // Create temporary builder adding only the commands that are valid for the target
- CommandLineBuilder builder = new(new Command(_rootBuilder.Command.Name));
- foreach (Command cmd in _rootBuilder.Command.Children.OfType<Command>())
+ /// <summary>
+ /// Displays the detailed help for a command
+ /// </summary>
+ /// <param name="commandName">name of the command or alias</param>
+ /// <param name="services">service provider</param>
+ /// <param name="consoleWidth">the width to format the help or int.MaxValue</param>
+ /// <returns>help text or null if not found</returns>
+ public string GetDetailedHelp(string commandName, IServiceProvider services, int consoleWidth)
+ {
+ if (string.IsNullOrWhiteSpace(commandName))
+ {
+ throw new ArgumentNullException(nameof(commandName));
+ }
+ List<string> messages = new();
+ foreach (CommandGroup group in _commandGroups)
+ {
+ if (group.TryGetCommand(commandName, out Command command))
{
- if (cmd.Handler is CommandHandler handler)
+ if (command.Handler is CommandHandler handler)
{
- if (handler.IsValidPlatform(target))
+ try
+ {
+ if (handler.IsCommandSupported(group.Parser, services))
+ {
+ return group.GetDetailedHelp(command, services, consoleWidth);
+ }
+ if (handler.FilterInvokeMessage != null)
+ {
+ messages.Add(handler.FilterInvokeMessage);
+ }
+ }
+ catch (CommandNotFoundException ex)
{
- builder.AddCommand(cmd);
+ messages.Add(ex.Message);
}
}
}
- command = builder.Command;
}
- Debug.Assert(command != null);
- IHelpBuilder helpBuilder = new LocalHelpBuilder(this, new LocalConsole(services), useHelpBuilder: true);
- helpBuilder.Write(command);
- return true;
+ if (messages.Count > 0)
+ {
+ return string.Concat(messages.Select(s => s + Environment.NewLine));
+ }
+ return null;
}
/// <summary>
- /// Does this command or alias exists?
- /// </summary>
- /// <param name="commandName">command or alias name</param>
- /// <returns>true if command exists</returns>
- public bool IsCommand(string commandName) => _rootBuilder.Command.Children.Contains(commandName);
-
- /// <summary>
- /// Enumerates all the command's name and help
+ /// Enumerates all the command's name, help and aliases
/// </summary>
- public IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands => _commandHandlers.Select((keypair) => (keypair.Value.Name, keypair.Value.Help, keypair.Value.Aliases));
+ public IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands =>
+ _commandGroups.SelectMany((group) => group.CommandHandlers).Select((handler) => (handler.Name, handler.Help, handler.Aliases));
/// <summary>
/// Add the commands and aliases attributes found in the type.
CommandAttribute[] commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: true);
foreach (CommandAttribute commandAttribute in commandAttributes)
{
- if ((commandAttribute.Flags & CommandFlags.Manual) == 0 || factory != null)
+ factory ??= (services) => Utilities.CreateInstance(type, services);
+
+ bool dup = true;
+ foreach (CommandGroup group in _commandGroups)
{
- factory ??= (services) => Utilities.CreateInstance(type, services);
- CreateCommand(baseType, commandAttribute, factory);
+ // If the group doesn't contain a duplicate command name, add it to that group
+ if (!group.Contains(commandAttribute.Name))
+ {
+ group.CreateCommand(baseType, commandAttribute, factory);
+ dup = false;
+ break;
+ }
+ }
+ // If this is a duplicate command, create a new group and add it to the beginning. The default group must be last.
+ if (dup)
+ {
+ CommandGroup group = new(_commandPrompt);
+ _commandGroups.Insert(0, group);
+ group.CreateCommand(baseType, commandAttribute, factory);
}
}
}
-
- // Build or re-build parser instance after all these commands and aliases are added
- FlushParser();
}
}
- private void CreateCommand(Type type, CommandAttribute commandAttribute, Func<IServiceProvider, object> factory)
+ /// <summary>
+ /// This groups like commands that may have the same name as another group or the default one.
+ /// </summary>
+ private sealed class CommandGroup
{
- Command command = new(commandAttribute.Name, commandAttribute.Help);
- List<(PropertyInfo, Option)> properties = new();
- List<(PropertyInfo, Argument)> arguments = new();
+ private Parser _parser;
+ private readonly CommandLineBuilder _rootBuilder;
+ private readonly Dictionary<string, CommandHandler> _commandHandlers = new();
- foreach (string alias in commandAttribute.Aliases)
+ /// <summary>
+ /// Create an instance of the command processor;
+ /// </summary>
+ /// <param name="commandPrompt">command prompted used in help message</param>
+ public CommandGroup(string commandPrompt = null)
{
- command.AddAlias(alias);
+ _rootBuilder = new CommandLineBuilder(new Command(commandPrompt));
}
- foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+ /// <summary>
+ /// Parse and execute the command line.
+ /// </summary>
+ /// <param name="commandLine">command line text</param>
+ /// <param name="services">services for the command</param>
+ /// <returns>true if command was found and executed without error</returns>
+ /// <exception cref="DiagnosticsException">parsing error</exception>
+ internal bool Execute(IReadOnlyList<string> commandLine, IServiceProvider services)
{
- ArgumentAttribute argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
- if (argumentAttribute != null)
- {
- IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
+ // Parse the command line and invoke the command
+ ParseResult parseResult = Parser.Parse(commandLine);
- Argument argument = new()
+ if (parseResult.Errors.Count > 0)
+ {
+ StringBuilder sb = new();
+ foreach (ParseError error in parseResult.Errors)
{
- Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
- Description = argumentAttribute.Help,
- ArgumentType = property.PropertyType,
- Arity = arity
- };
- command.AddArgument(argument);
- arguments.Add((property, argument));
+ sb.AppendLine(error.Message);
+ }
+ string helpText = GetDetailedHelp(parseResult.CommandResult.Command, services, int.MaxValue);
+ throw new CommandParsingException(sb.ToString(), helpText);
}
else
{
- OptionAttribute optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
- if (optionAttribute != null)
+ if (parseResult.CommandResult.Command is Command command)
{
- Option option = new(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help)
+ if (command.Handler is CommandHandler handler)
{
- Argument = new Argument { ArgumentType = property.PropertyType }
- };
- command.AddOption(option);
- properties.Add((property, option));
+ InvocationContext context = new(parseResult, new LocalConsole(services.GetService<IConsoleService>()));
+ handler.Invoke(context, services);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Build/return parser
+ /// </summary>
+ internal Parser Parser => _parser ??= _rootBuilder.Build();
+
+ /// <summary>
+ /// Returns all the command handler instances
+ /// </summary>
+ internal IEnumerable<CommandHandler> CommandHandlers => _commandHandlers.Values;
+
+ /// <summary>
+ /// Returns true if command or command alias is found
+ /// </summary>
+ internal bool Contains(string commandName) => _rootBuilder.Command.Children.Contains(commandName);
+
+ /// <summary>
+ /// Returns the command handler for the command or command alias
+ /// </summary>
+ /// <param name="commandName">command or alias</param>
+ /// <param name="handler">handler instance</param>
+ /// <returns>true if found</returns>
+ internal bool TryGetCommandHandler(string commandName, out CommandHandler handler)
+ {
+ handler = null;
+ if (TryGetCommand(commandName, out Command command))
+ {
+ handler = command.Handler as CommandHandler;
+ }
+ return handler != null;
+ }
+
+ /// <summary>
+ /// Returns the command instance for the command or command alias
+ /// </summary>
+ /// <param name="commandName">command or alias</param>
+ /// <param name="command">command instance</param>
+ /// <returns>true if found</returns>
+ internal bool TryGetCommand(string commandName, out Command command)
+ {
+ command = _rootBuilder.Command.Children.GetByAlias(commandName) as Command;
+ return command != null;
+ }
+
+ /// <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>
+ internal void AddCommands(Type type, Func<IServiceProvider, object> factory)
+ {
+ for (Type baseType = type; baseType != null; baseType = baseType.BaseType)
+ {
+ if (baseType == typeof(CommandBase))
+ {
+ break;
+ }
+ CommandAttribute[] commandAttributes = (CommandAttribute[])baseType.GetCustomAttributes(typeof(CommandAttribute), inherit: false);
+ foreach (CommandAttribute commandAttribute in commandAttributes)
+ {
+ factory ??= (services) => Utilities.CreateInstance(type, services);
+ CreateCommand(baseType, commandAttribute, factory);
+ }
+ }
+
+ // Build or re-build parser instance after all these commands and aliases are added
+ FlushParser();
+ }
+
+ internal void CreateCommand(Type type, CommandAttribute commandAttribute, Func<IServiceProvider, object> factory)
+ {
+ Command command = new(commandAttribute.Name, commandAttribute.Help);
+ List<(PropertyInfo, Argument)> arguments = new();
+ List<(PropertyInfo, Option)> options = new();
- foreach (string alias in optionAttribute.Aliases)
+ foreach (string alias in commandAttribute.Aliases)
+ {
+ command.AddAlias(alias);
+ }
+
+ foreach (PropertyInfo property in type.GetProperties().Where(p => p.CanWrite))
+ {
+ ArgumentAttribute argumentAttribute = (ArgumentAttribute)property.GetCustomAttributes(typeof(ArgumentAttribute), inherit: false).SingleOrDefault();
+ if (argumentAttribute != null)
+ {
+ IArgumentArity arity = property.PropertyType.IsArray ? ArgumentArity.ZeroOrMore : ArgumentArity.ZeroOrOne;
+
+ Argument argument = new()
+ {
+ Name = argumentAttribute.Name ?? property.Name.ToLowerInvariant(),
+ Description = argumentAttribute.Help,
+ ArgumentType = property.PropertyType,
+ Arity = arity
+ };
+ command.AddArgument(argument);
+ arguments.Add((property, argument));
+ }
+ else
+ {
+ OptionAttribute optionAttribute = (OptionAttribute)property.GetCustomAttributes(typeof(OptionAttribute), inherit: false).SingleOrDefault();
+ if (optionAttribute != null)
{
- option.AddAlias(alias);
+ Option option = new(optionAttribute.Name ?? BuildOptionAlias(property.Name), optionAttribute.Help)
+ {
+ Argument = new Argument { ArgumentType = property.PropertyType }
+ };
+ command.AddOption(option);
+ options.Add((property, option));
+
+ foreach (string alias in optionAttribute.Aliases)
+ {
+ option.AddAlias(alias);
+ }
}
}
}
+
+ CommandHandler handler = new(commandAttribute, arguments, options, type, factory);
+ _commandHandlers.Add(command.Name, handler);
+ command.Handler = handler;
+ _rootBuilder.AddCommand(command);
+
+ // Build or re-build parser instance after this command is added
+ FlushParser();
}
- CommandHandler handler = new(commandAttribute, arguments, properties, type, factory);
- _commandHandlers.Add(command.Name, handler);
- command.Handler = handler;
- _rootBuilder.AddCommand(command);
- }
+ internal string GetDetailedHelp(ICommand command, IServiceProvider services, int windowWidth)
+ {
+ CaptureConsole console = new();
- private Parser Parser => _parser ??= _rootBuilder.Build();
+ // Get the command help
+ HelpBuilder helpBuilder = new(console, maxWidth: windowWidth);
+ helpBuilder.Write(command);
- private void FlushParser() => _parser = null;
+ // Get the detailed help if any
+ if (TryGetCommandHandler(command.Name, out CommandHandler handler))
+ {
+ string helpText = handler.GetDetailedHelp(Parser, services);
+ if (helpText is not null)
+ {
+ console.Out.Write(helpText);
+ }
+ }
- private static string BuildOptionAlias(string parameterName)
- {
- if (string.IsNullOrWhiteSpace(parameterName))
+ return console.ToString();
+ }
+
+ private void FlushParser() => _parser = null;
+
+ private static string BuildOptionAlias(string parameterName)
{
- throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName));
+ if (string.IsNullOrWhiteSpace(parameterName))
+ {
+ throw new ArgumentException("Value cannot be null or whitespace.", nameof(parameterName));
+ }
+ return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}";
}
- return parameterName.Length > 1 ? $"--{parameterName.ToKebabCase()}" : $"-{parameterName.ToLowerInvariant()}";
}
/// <summary>
{
private readonly CommandAttribute _commandAttribute;
private readonly IEnumerable<(PropertyInfo Property, Argument Argument)> _arguments;
- private readonly IEnumerable<(PropertyInfo Property, Option Option)> _properties;
+ private readonly IEnumerable<(PropertyInfo Property, Option Option)> _options;
private readonly Func<IServiceProvider, object> _factory;
private readonly MethodInfo _methodInfo;
private readonly MethodInfo _methodInfoHelp;
+ private readonly MethodInfo _methodInfoFilter;
+ private readonly FilterInvokeAttribute _filterInvokeAttribute;
public CommandHandler(
CommandAttribute commandAttribute,
IEnumerable<(PropertyInfo, Argument)> arguments,
- IEnumerable<(PropertyInfo, Option)> properties,
+ IEnumerable<(PropertyInfo, Option)> options,
Type type,
Func<IServiceProvider, object> factory)
{
_commandAttribute = commandAttribute;
_arguments = arguments;
- _properties = properties;
+ _options = options;
_factory = factory;
- _methodInfo = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<CommandInvokeAttribute>() != null).SingleOrDefault() ??
+ // Now search for the command, help and filter attributes in the command type
+ foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy))
+ {
+ if (methodInfo.GetCustomAttribute<CommandInvokeAttribute>() != null)
+ {
+ if (_methodInfo != null)
+ {
+ throw new ArgumentException($"Multiple CommandInvokeAttribute's found in {type}");
+ }
+ _methodInfo = methodInfo;
+ }
+ if (methodInfo.GetCustomAttribute<HelpInvokeAttribute>() != null)
+ {
+ if (_methodInfoHelp != null)
+ {
+ throw new ArgumentException($"Multiple HelpInvokeAttribute's found in {type}");
+ }
+ if (methodInfo.ReturnType != typeof(string))
+ {
+ throw new ArgumentException($"HelpInvokeAttribute doesn't return string in {type}");
+ }
+ _methodInfoHelp = methodInfo;
+ }
+ FilterInvokeAttribute filterInvokeAttribute = methodInfo.GetCustomAttribute<FilterInvokeAttribute>();
+ if (filterInvokeAttribute != null)
+ {
+ if (_methodInfoFilter != null)
+ {
+ throw new ArgumentException($"Multiple FilterInvokeAttribute's found in {type}");
+ }
+ if (methodInfo.ReturnType != typeof(bool))
+ {
+ throw new ArgumentException($"FilterInvokeAttribute doesn't return bool in {type}");
+ }
+ _filterInvokeAttribute = filterInvokeAttribute;
+ _methodInfoFilter = methodInfo;
+ }
+ }
+ if (_methodInfo == null)
+ {
throw new ArgumentException($"No command invoke method found in {type}");
-
- _methodInfoHelp = type.GetMethods().Where((methodInfo) => methodInfo.GetCustomAttribute<HelpInvokeAttribute>() != null).SingleOrDefault();
+ }
}
Task<int> ICommandHandler.InvokeAsync(InvocationContext context)
/// </summary>
internal string Help => _commandAttribute.Help;
+ /// <summary>
+ /// Filter invoke message or null if no attribute or message
+ /// </summary>
+ internal string FilterInvokeMessage => _filterInvokeAttribute?.Message;
+
/// <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.
+ /// Returns the list of arguments
/// </summary>
- internal bool IsValidPlatform(ITarget target)
- {
- if ((_commandAttribute.Flags & CommandFlags.Global) != 0)
- {
- return true;
- }
- if (target != null)
- {
- if (target.OperatingSystem == OSPlatform.Windows)
- {
- return (_commandAttribute.Flags & CommandFlags.Windows) != 0;
- }
- if (target.OperatingSystem == OSPlatform.Linux)
- {
- return (_commandAttribute.Flags & CommandFlags.Linux) != 0;
- }
- if (target.OperatingSystem == OSPlatform.OSX)
- {
- return (_commandAttribute.Flags & CommandFlags.OSX) != 0;
- }
- }
- return false;
- }
+ internal IEnumerable<Argument> Arguments => _arguments.Select((a) => a.Argument);
+
+ /// <summary>
+ /// Returns true is the command is supported by the command filter. Calls the FilterInvokeAttribute marked method.
+ /// </summary>
+ internal bool IsCommandSupported(Parser parser, IServiceProvider services) => _methodInfoFilter == null || (bool)Invoke(_methodInfoFilter, context: null, parser, services);
/// <summary>
/// Execute the command synchronously.
/// <param name="services">service provider</param>
internal void Invoke(InvocationContext context, IServiceProvider services) => Invoke(_methodInfo, context, context.Parser, services);
+ /// <summary>
+ /// Return the various ways the command can be invoked. For building the help text.
+ /// </summary>
+ internal string HelpInvocation
+ {
+ get
+ {
+ IEnumerable<string> rawAliases = new string[] { Name }.Concat(Aliases);
+ string invocation = string.Join(", ", rawAliases);
+ foreach (Argument argument in Arguments)
+ {
+ string argumentDescriptor = argument.Name;
+ if (!string.IsNullOrWhiteSpace(argumentDescriptor))
+ {
+ invocation = $"{invocation} <{argumentDescriptor}>";
+ }
+ }
+ return invocation;
+ }
+ }
+
/// <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(Parser parser, IServiceProvider services)
+ internal string GetDetailedHelp(Parser parser, IServiceProvider services)
{
if (_methodInfoHelp == null)
{
- return false;
+ return null;
}
// The InvocationContext is null so the options and arguments in the
// 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, parser, services);
- return true;
+ return (string)Invoke(_methodInfoHelp, context: null, parser, services);
}
- private void Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services)
+ private object Invoke(MethodInfo methodInfo, InvocationContext context, Parser parser, IServiceProvider services)
{
- object instance = _factory(services);
- SetProperties(context, parser, instance);
- Utilities.Invoke(methodInfo, instance, services);
+ object instance = null;
+ if (!methodInfo.IsStatic)
+ {
+ instance = _factory(services);
+ SetProperties(context, parser, instance);
+ }
+ return Utilities.Invoke(methodInfo, instance, services);
}
private void SetProperties(InvocationContext context, Parser parser, object instance)
}
// Now initialize the option and service properties from the default and command line options
- foreach ((PropertyInfo Property, Option Option) property in _properties)
+ foreach ((PropertyInfo Property, Option Option) option in _options)
{
- object value = property.Property.GetValue(instance);
+ object value = option.Property.GetValue(instance);
- if (property.Option != null)
+ if (defaultParseResult != null)
{
- if (defaultParseResult != null)
+ OptionResult defaultOptionResult = defaultParseResult.FindResultFor(option.Option);
+ if (defaultOptionResult != null)
{
- OptionResult defaultOptionResult = defaultParseResult.FindResultFor(property.Option);
- if (defaultOptionResult != null)
- {
- value = defaultOptionResult.GetValueOrDefault();
- }
+ value = defaultOptionResult.GetValueOrDefault();
}
- if (context != null)
+ }
+ if (context != null)
+ {
+ OptionResult optionResult = context.ParseResult.FindResultFor(option.Option);
+ if (optionResult != null)
{
- OptionResult optionResult = context.ParseResult.FindResultFor(property.Option);
- if (optionResult != null)
- {
- value = optionResult.GetValueOrDefault();
- }
+ value = optionResult.GetValueOrDefault();
}
}
- property.Property.SetValue(instance, value);
+ option.Property.SetValue(instance, value);
}
// Initialize any argument properties from the default and command line arguments
}
/// <summary>
- /// Local help builder that allows commands to provide more detailed help
- /// text via the "InvokeHelp" function.
+ /// IConsole implementation that captures all the output into a string.
/// </summary>
- private sealed class LocalHelpBuilder : IHelpBuilder
+ private sealed class CaptureConsole : IConsole
{
- private readonly CommandService _commandService;
- private readonly LocalConsole _console;
- private readonly bool _useHelpBuilder;
+ private readonly StringBuilder _builder = new();
- public LocalHelpBuilder(CommandService commandService, IConsole console, bool useHelpBuilder)
+ public CaptureConsole()
{
- _commandService = commandService;
- _console = (LocalConsole)console;
- _useHelpBuilder = useHelpBuilder;
+ Out = Error = new StandardStreamWriter((text) => _builder.Append(text));
}
- void IHelpBuilder.Write(ICommand command)
- {
- bool useHelpBuilder = _useHelpBuilder;
- if (_commandService._commandHandlers.TryGetValue(command.Name, out CommandHandler handler))
- {
- if (handler.InvokeHelp(_commandService.Parser, _console.Services))
- {
- return;
- }
- useHelpBuilder = true;
- }
- if (useHelpBuilder)
- {
- HelpBuilder helpBuilder = new(_console, maxWidth: _console.ConsoleService.WindowWidth);
- helpBuilder.Write(command);
- }
- }
+ public override string ToString() => _builder.ToString();
+
+ #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; } }
+
+ #endregion
}
/// <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.
+ /// This class wraps the IConsoleService and provides the IConsole interface for System.CommandLine.
/// </summary>
private sealed class LocalConsole : IConsole
{
- private IConsoleService _console;
+ private readonly IConsoleService _consoleService;
- public LocalConsole(IServiceProvider services)
+ public LocalConsole(IConsoleService consoleService)
{
- Services = services;
- Out = new StandardStreamWriter(ConsoleService.Write);
- Error = new StandardStreamWriter(ConsoleService.WriteError);
- }
-
- internal readonly IServiceProvider Services;
-
- internal IConsoleService ConsoleService
- {
- get
- {
- _console ??= Services.GetService<IConsoleService>();
- return _console;
- }
+ _consoleService = consoleService;
+ Out = new StandardStreamWriter(_consoleService.Write);
+ Error = new StandardStreamWriter(_consoleService.WriteError);
}
#region IConsole
bool IStandardIn.IsInputRedirected { get { return false; } }
- private sealed class StandardStreamWriter : IStandardStreamWriter
- {
- private readonly Action<string> _write;
+ #endregion
+ }
- public StandardStreamWriter(Action<string> write) => _write = write;
+ private sealed class StandardStreamWriter : IStandardStreamWriter
+ {
+ private readonly Action<string> _write;
- void IStandardStreamWriter.Write(string value) => _write(value);
- }
+ public StandardStreamWriter(Action<string> write) => _write = write;
- #endregion
+ void IStandardStreamWriter.Write(string value) => _write(value);
}
}
}
public ExtensionLoadContext(string extensionPath)
{
+ Trace.TraceInformation($"ExtensionLoadContext: {extensionPath}");
_extensionPath = extensionPath;
}
{
throw new InvalidOperationException($"Extension assembly reference version not supported for {assemblyName.Name} {assemblyName.Version}");
}
+ Trace.TraceInformation($"ExtensionLoadContext: loading SOS assembly {assembly.CodeBase}");
return assembly;
}
else if (_extensionPaths.TryGetValue(assemblyName.Name, out string path))
{
+ Trace.TraceInformation($"ExtensionLoadContext: loading from extension path {path}");
return LoadFromAssemblyPath(path);
}
+ Trace.TraceInformation($"ExtensionLoadContext: returning null {assemblyName}");
return null;
}
}
using System.Reflection;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
using Microsoft.FileFormats;
using Microsoft.FileFormats.ELF;
using Microsoft.FileFormats.MachO;
/// <remarks>
/// This function is neither commutative nor associative; the hash codes must be combined in
/// a deterministic order. Do not use this when hashing collections whose contents are
- /// nondeterministically ordered!
+ /// non-deterministically ordered!
/// </remarks>
public static int CombineHashCodes(int hashCode0, int hashCode1)
{
return arguments;
}
}
+
+ public class CaptureConsoleService : IConsoleService
+ {
+ private readonly StringBuilder _builder = new();
+
+ public CaptureConsoleService()
+ {
+ }
+
+ public void Clear() => _builder.Clear();
+
+ public override string ToString() => _builder.ToString();
+
+ #region IConsoleService
+
+ public void Write(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public void WriteWarning(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public void WriteError(string text)
+ {
+ _builder.Append(text);
+ }
+
+ public bool SupportsDml => false;
+
+ public void WriteDml(string text) => throw new NotSupportedException();
+
+ public void WriteDmlExec(string text, string _) => throw new NotSupportedException();
+
+ public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
+
+ int IConsoleService.WindowWidth => int.MaxValue;
+
+ #endregion
+ }
}
namespace Microsoft.Diagnostics.DebugServices
{
- /// <summary>
- /// Command flags to filter by OS Platforms, control scope and how the command is registered.
- /// </summary>
- [Flags]
- public enum CommandFlags : byte
- {
- Windows = 0x01,
- Linux = 0x02,
- OSX = 0x04,
-
- /// <summary>
- /// Command is supported when there is no target
- /// </summary>
- Global = 0x08,
-
- /// <summary>
- /// Command is not added through reflection, but manually with command service API.
- /// </summary>
- Manual = 0x10,
-
- /// <summary>
- /// Default. All operating system, but target is required
- /// </summary>
- Default = Windows | Linux | OSX
- }
-
/// <summary>
/// Marks the class as a Command.
/// </summary>
/// </summary>
public string[] Aliases = Array.Empty<string>();
- /// <summary>
- /// Command flags to filter by OS Platforms, control scope and how the command is registered.
- /// </summary>
- public CommandFlags Flags = CommandFlags.Default;
-
/// <summary>
/// A string of options that are parsed before the command line options
/// </summary>
}
/// <summary>
- /// Marks the function to invoke to display alternate help for command.
+ /// Marks the function to invoke to return the alternate help for command. The function returns
+ /// a string. The Argument and Option properties of the command are not set.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class HelpInvokeAttribute : Attribute
{
}
+
+ /// <summary>
+ /// Marks the function to invoke to filter a command. The function returns a bool; true if
+ /// the command is supported. The Argument and Option properties of the command are not set.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public class FilterInvokeAttribute : Attribute
+ {
+ /// <summary>
+ /// Message to display if the filter fails
+ /// </summary>
+ public string Message;
+ }
}
}
/// <summary>
- /// Thrown if a command is not supported on the configuration, platform or runtime
+ /// Thrown if a command is not found.
/// </summary>
- public class CommandNotSupportedException : DiagnosticsException
+ public class CommandNotFoundException : DiagnosticsException
{
- public CommandNotSupportedException()
- : base()
+ public const string NotFoundMessage = $"Unrecognized SOS command";
+
+ public CommandNotFoundException(string message)
+ : base(message)
+ {
+ }
+
+ public CommandNotFoundException(string message, Exception innerException)
+ : base(message, innerException)
{
}
+ }
+
+ /// <summary>
+ /// Thrown if a command is not found.
+ /// </summary>
+ public class CommandParsingException : DiagnosticsException
+ {
+ /// <summary>
+ /// The detailed help of the command
+ /// </summary>
+ public string DetailedHelp { get; }
- public CommandNotSupportedException(string message)
+ public CommandParsingException(string message, string detailedHelp)
: base(message)
{
+ DetailedHelp = detailedHelp;
}
- public CommandNotSupportedException(string message, Exception innerException)
+ public CommandParsingException(string message, string detailedHelp, Exception innerException)
: base(message, innerException)
{
+ DetailedHelp = detailedHelp;
}
}
}
public interface ICommandService
{
/// <summary>
- /// Enumerates all the command's name and help
+ /// Enumerates all the command's name, help and aliases
/// </summary>
IEnumerable<(string name, string help, IEnumerable<string> aliases)> Commands { get; }
void AddCommands(Type type);
/// <summary>
- /// Displays the help for a command
+ /// Gets help for all of the commands
+ /// </summary>
+ /// <param name="services">service provider</param>
+ /// <returns>command invocation and help enumeration</returns>
+ public IEnumerable<(string Invocation, string Help)> GetAllCommandHelp(IServiceProvider services);
+
+ /// <summary>
+ /// Displays the detailed help for a command
/// </summary>
/// <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>
- bool DisplayHelp(string commandName, IServiceProvider services);
+ /// <param name="consoleWidth">the width to format the help or int.MaxValue</param>
+ /// <returns>help text or null if not found</returns>
+ string GetDetailedHelp(string commandName, IServiceProvider services, int consoleWidth);
}
}
{
/// <summary>
/// The interface or type to register the provider. If null, the provider type registered will be
- /// he class itself or the return type of the method.
+ /// the class itself or the return type of the method.
/// </summary>
public Type Type { get; set; }
/// </summary>
/// <param name="parent">search this provider if service isn't found in this instance or null</param>
/// <param name="factories">service factories to initialize provider or null</param>
- public ServiceContainer(IServiceProvider parent, Dictionary<Type, ServiceFactory> factories)
+ public ServiceContainer(IServiceProvider parent, Dictionary<Type, ServiceFactory> factories = null)
{
- Debug.Assert(factories != null);
_parent = parent;
_factories = factories;
_instances = new Dictionary<Type, object>();
{
return service;
}
- if (_factories.TryGetValue(type, out ServiceFactory factory))
+ if (_factories != null && _factories.TryGetValue(type, out ServiceFactory factory))
{
service = factory(this);
_instances.Add(type, service);
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "analyzeoom", Help = "Displays the info of the last OOM that occurred on an allocation request to the GC heap.")]
- public class AnalyzeOOMCommand : CommandBase
+ [Command(Name = "analyzeoom", Aliases = new[] { "AnalyzeOOM" }, Help = "Displays the info of the last OOM that occurred on an allocation request to the GC heap.")]
+ public class AnalyzeOOMCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
bool foundOne = false;
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "assemblies", Aliases = new[] { "clrmodules" }, Help = "Lists the managed assemblies in the process.")]
+ public class AssembliesCommand : ClrRuntimeCommandBase
+ {
+ [ServiceImport]
+ public IModuleService ModuleService { get; set; }
+
+ [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on assembly name (path not included).")]
+ public string AssemblyName { get; set; }
+
+ [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the assemblies.")]
+ public bool Verbose { get; set; }
+
+ public override void Invoke()
+ {
+ Regex regex = AssemblyName is not null ? new Regex(AssemblyName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null;
+ foreach (ClrModule module in Runtime.EnumerateModules())
+ {
+ if (regex is null || !string.IsNullOrEmpty(module.Name) && regex.IsMatch(Path.GetFileName(module.Name)))
+ {
+ if (Verbose)
+ {
+ WriteLine("{0}{1}", module.Name, module.IsDynamic ? "(Dynamic)" : "");
+ WriteLine(" AssemblyName: {0}", module.AssemblyName);
+ WriteLine(" ImageBase: {0:X16}", module.ImageBase);
+ WriteLine(" Size: {0:X8}", module.Size);
+ WriteLine(" ModuleAddress: {0:X16}", module.Address);
+ WriteLine(" AssemblyAddress: {0:X16}", module.AssemblyAddress);
+ WriteLine(" IsPEFile: {0}", module.IsPEFile);
+ WriteLine(" Layout: {0}", module.Layout);
+ WriteLine(" IsDynamic: {0}", module.IsDynamic);
+ WriteLine(" MetadataAddress: {0:X16}", module.MetadataAddress);
+ WriteLine(" MetadataSize: {0:X16}", module.MetadataLength);
+ WriteLine(" PdbInfo: {0}", module.Pdb?.ToString() ?? "<none>");
+ Version version = null;
+ try
+ {
+ version = ModuleService.GetModuleFromBaseAddress(module.ImageBase).GetVersionData();
+ }
+ catch (DiagnosticsException)
+ {
+ }
+ WriteLine(" Version: {0}", version?.ToString() ?? "<none>");
+ }
+ else
+ {
+ WriteLine("{0:X16} {1:X8} {2}{3}", module.ImageBase, module.Size, module.Name, module.IsDynamic ? "(Dynamic)" : "");
+ }
+ }
+ }
+ }
+ }
+}
private readonly ClrHeap _heap;
[ServiceExport(Scope = ServiceScope.Runtime)]
- public static ClrMDHelper Create([ServiceImport(Optional = true)] ClrRuntime clrRuntime)
+ public static ClrMDHelper TryCreate([ServiceImport(Optional = true)] ClrRuntime clrRuntime)
{
return clrRuntime != null ? new ClrMDHelper(clrRuntime) : 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.
+
+using Microsoft.Diagnostics.DebugServices;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ public abstract class ClrMDHelperCommandBase : CommandBase
+ {
+ /// <summary>
+ /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD.
+ /// </summary>
+ [ServiceImport(Optional = true)]
+ public ClrMDHelper Helper { get; set; }
+
+ [FilterInvoke(Message = ClrRuntimeCommandBase.RuntimeNotFoundMessage)]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] ClrMDHelper helper) => helper != 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.
-
-using System;
-using System.IO;
-using System.Text.RegularExpressions;
-using Microsoft.Diagnostics.DebugServices;
-using Microsoft.Diagnostics.Runtime;
-
-namespace Microsoft.Diagnostics.ExtensionCommands
-{
- [Command(Name = "clrmodules", Help = "Lists the managed modules in the process.")]
- public class ClrModulesCommand : CommandBase
- {
- [ServiceImport(Optional = true)]
- public ClrRuntime Runtime { get; set; }
-
- [ServiceImport]
- public IModuleService ModuleService { get; set; }
-
- [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on module name (path not included).")]
- public string ModuleName { get; set; }
-
- [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays detailed information about the modules.")]
- public bool Verbose { get; set; }
-
- public override void Invoke()
- {
- if (Runtime == null)
- {
- throw new DiagnosticsException("No CLR runtime set");
- }
- Regex regex = ModuleName is not null ? new Regex(ModuleName, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) : null;
- foreach (ClrModule module in Runtime.EnumerateModules())
- {
- if (regex is null || !string.IsNullOrEmpty(module.Name) && regex.IsMatch(Path.GetFileName(module.Name)))
- {
- if (Verbose)
- {
- WriteLine("{0}{1}", module.Name, module.IsDynamic ? "(Dynamic)" : "");
- WriteLine(" AssemblyName: {0}", module.AssemblyName);
- WriteLine(" ImageBase: {0:X16}", module.ImageBase);
- WriteLine(" Size: {0:X8}", module.Size);
- WriteLine(" ModuleAddress: {0:X16}", module.Address);
- WriteLine(" AssemblyAddress: {0:X16}", module.AssemblyAddress);
- WriteLine(" IsPEFile: {0}", module.IsPEFile);
- WriteLine(" Layout: {0}", module.Layout);
- WriteLine(" IsDynamic: {0}", module.IsDynamic);
- WriteLine(" MetadataAddress: {0:X16}", module.MetadataAddress);
- WriteLine(" MetadataSize: {0:X16}", module.MetadataLength);
- WriteLine(" PdbInfo: {0}", module.Pdb?.ToString() ?? "<none>");
- Version version = null;
- try
- {
- version = ModuleService.GetModuleFromBaseAddress(module.ImageBase).GetVersionData();
- }
- catch (DiagnosticsException)
- {
- }
- WriteLine(" Version: {0}", version?.ToString() ?? "<none>");
- }
- else
- {
- WriteLine("{0:X16} {1:X8} {2}{3}", module.ImageBase, module.Size, module.Name, module.IsDynamic ? "(Dynamic)" : "");
- }
- }
- }
- }
- }
-}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ public abstract class ClrRuntimeCommandBase : CommandBase
+ {
+ public const string RuntimeNotFoundMessage = "No CLR runtime found. This means that a .NET runtime module or the DAC for the runtime can not be found or downloaded.";
+
+ [ServiceImport(Optional = true)]
+ public ClrRuntime Runtime { get; set; }
+
+ [FilterInvoke(Message = RuntimeNotFoundMessage)]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime) => runtime != null;
+ }
+}
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = CommandName, Aliases = new string[] { "DumpAsync" }, Help = "Displays information about async \"stacks\" on the garbage-collected heap.")]
- public sealed class DumpAsyncCommand : ExtensionCommandBase
+ public sealed class DumpAsyncCommand : ClrRuntimeCommandBase
{
/// <summary>The name of the command.</summary>
private const string CommandName = "dumpasync";
/// <summary>Indent width.</summary>
private const int TabWidth = 2;
+
/// <summary>The command invocation syntax when used in Debugger Markup Language (DML) commands.</summary>
private const string DmlCommandInvoke = $"!{CommandName}";
- /// <summary>The help text to render when asked for help.</summary>
- private static readonly string s_detailedHelpText =
- $"Usage: {CommandName} [--stats] [--coalesce] [--address <object address>] [--methodtable <mt address>] [--type <partial type name>] [--tasks] [--completed] [--fields]" + Environment.NewLine +
- Environment.NewLine +
- "Displays information about async \"stacks\" on the garbage-collected heap. Stacks" + Environment.NewLine +
- "are synthesized by finding all task objects (including async state machine box" + Environment.NewLine +
- "objects) on the GC heap and chaining them together based on continuations." + Environment.NewLine +
- Environment.NewLine +
- "Options:" + Environment.NewLine +
- " --stats Summarize all async frames found rather than showing detailed stacks." + Environment.NewLine +
- " --coalesce Coalesce stacks and portions of stacks that are the same." + Environment.NewLine +
- " --address Only show stacks that include the object with the specified address." + Environment.NewLine +
- " --methodtable Only show stacks that include objects with the specified method table." + Environment.NewLine +
- " --type Only show stacks that include objects whose type includes the specified name in its name." + Environment.NewLine +
- " --tasks Include stacks that contain only non-state machine task objects." + Environment.NewLine +
- " --completed Include completed tasks in stacks." + Environment.NewLine +
- " --fields Show fields for each async stack frame." + Environment.NewLine +
- Environment.NewLine +
- "Examples:" + Environment.NewLine +
- $"Summarize all async frames associated with a specific method table address: !{CommandName} --stats --methodtable 0x00007ffbcfbe0970" + Environment.NewLine +
- $"Show all stacks coalesced by common frames: !{CommandName} --coalesce" + Environment.NewLine +
- $"Show each stack that includes \"ReadAsync\": !{CommandName} --type ReadAsync" + Environment.NewLine +
- $"Show each stack that includes an object at a specific address, and include fields: !{CommandName} --address 0x000001264adce778 --fields";
-
- /// <summary>Gets the runtime for the process. Set by the command framework.</summary>
- [ServiceImport(Optional = true)]
- public ClrRuntime? Runtime { get; set; }
-
-
/// <summary>Gets whether to only show stacks that include the object with the specified address.</summary>
[Option(Name = "--address", Aliases = new string[] { "-addr" }, Help = "Only show stacks that include the object with the specified address.")]
public string? ObjectAddress
public bool CoalesceStacks { get; set; }
/// <summary>Invokes the command.</summary>
- public override void ExtensionInvoke()
+ public override void Invoke()
{
- ClrRuntime? runtime = Runtime;
- if (runtime is null)
- {
- WriteLineError("Unable to access runtime.");
- return;
- }
-
+ ClrRuntime runtime = Runtime;
ClrHeap heap = runtime.Heap;
if (!heap.CanWalkHeap)
{
- WriteLineError("Unable to examine the heap.");
- return;
+ throw new DiagnosticsException("Unable to examine the heap.");
}
ClrType? taskType = runtime.BaseClassLibrary.GetTypeByName("System.Threading.Tasks.Task");
if (taskType is null)
{
- WriteLineError("Unable to find required type.");
- return;
+ throw new DiagnosticsException("Unable to find required type.");
}
ClrStaticField? taskCompletionSentinelType = taskType.GetStaticFieldByName("s_taskCompletionSentinel");
}
/// <summary>Gets detailed help for the command.</summary>
- protected override string GetDetailedHelp() => s_detailedHelpText;
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"Displays information about async ""stacks"" on the garbage-collected heap. Stacks
+are synthesized by finding all task objects (including async state machine box
+objects) on the GC heap and chaining them together based on continuations.
+
+Examples:
+ Summarize all async frames associated with a specific method table address: dumpasync --stats --methodtable 0x00007ffbcfbe0970
+ Show all stacks coalesced by common frames: dumpasync --coalesce
+ Show each stack that includes ""ReadAsync"": dumpasync --type ReadAsync
+ Show each stack that includes an object at a specific address, and include fields: dumpasync --address 0x000001264adce778 --fields
+";
/// <summary>Represents an async object to be used as a frame in an async "stack".</summary>
private sealed class AsyncObject
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "dumpconcurrentdictionary", Aliases = new string[] { "dcd" }, Help = "Displays concurrent dictionary content.")]
- public class DumpConcurrentDictionaryCommand : ExtensionCommandBase
+ public class DumpConcurrentDictionaryCommand : ClrMDHelperCommandBase
{
[Argument(Help = "The address of a ConcurrentDictionary object.")]
public string Address { get; set; }
- [ServiceImport]
+ [ServiceImport(Optional = true)]
public ClrRuntime Runtime { get; set; }
- public override void ExtensionInvoke()
+ public override void Invoke()
{
if (string.IsNullOrEmpty(Address))
{
- WriteLine("Missing ConcurrentDictionary address...");
- return;
+ throw new DiagnosticsException("Missing ConcurrentDictionary address...");
}
if (!TryParseAddress(Address, out ulong address))
{
- WriteLine("Hexadecimal address expected...");
- return;
+ throw new DiagnosticsException("Hexadecimal address expected...");
}
ClrHeap heap = Runtime.Heap;
ClrType type = heap.GetObjectType(address);
if (type?.Name is null)
{
- WriteLine($"{Address:x16} is not referencing an object...");
- return;
+ throw new DiagnosticsException($"{Address:x16} is not referencing an object...");
}
if (!type.Name.StartsWith("System.Collections.Concurrent.ConcurrentDictionary<"))
{
- WriteLine($"{Address:x16} is not a ConcurrentDictionary but an instance of {type.Name}...");
- return;
+ throw new DiagnosticsException($"{Address:x16} is not a ConcurrentDictionary but an instance of {type.Name}...");
}
WriteLine($"{type.Name}");
WriteLine(string.Empty);
}
- protected override string GetDetailedHelp()
- {
- return
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
@"-------------------------------------------------------------------------------
DumpConcurrentDictionary
Lists all items (key/value pairs) in the given concurrent dictionary.
- In case of reference types, the command to dump each object is shown (e.g. dumpobj <[item] address>).
- For value types, the command to dump each value type is shown (e.g. dumpvc <the Element Methodtable> <[item] address>).
";
- }
private static string Truncate(string str, int nbMaxChars)
{
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "dumpconcurrentqueue", Aliases = new string[] { "dcq" }, Help = "Displays concurrent queue content.")]
- public class DumpConcurrentQueueCommand : ExtensionCommandBase
+ public class DumpConcurrentQueueCommand : ClrMDHelperCommandBase
{
[Argument(Help = "The address of a ConcurrentQueue object.")]
public string Address { get; set; }
- [ServiceImport]
+ [ServiceImport(Optional = true)]
public ClrRuntime Runtime { get; set; }
- public override void ExtensionInvoke()
+ public override void Invoke()
{
if (string.IsNullOrEmpty(Address))
{
- WriteLine("Missing ConcurrentQueue address...");
- return;
+ throw new DiagnosticsException("Missing ConcurrentQueue address...");
}
if (!TryParseAddress(Address, out ulong address))
{
- WriteLine("Hexadecimal address expected...");
- return;
+ throw new DiagnosticsException("Hexadecimal address expected...");
}
ClrHeap heap = Runtime.Heap;
ClrType type = heap.GetObjectType(address);
if (type == null)
{
- WriteLine($"{Address:x16} is not referencing an object...");
- return;
+ throw new DiagnosticsException($"{Address:x16} is not referencing an object...");
}
-
if (!type.Name.StartsWith("System.Collections.Concurrent.ConcurrentQueue<"))
{
- WriteLine($"{Address:x16} is not a ConcurrentQueue but an instance of {type.Name}...");
- return;
+ throw new DiagnosticsException($"{Address:x16} is not a ConcurrentQueue but an instance of {type.Name}...");
}
WriteLine($"{type.Name}");
WriteLine("");
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+DumpConcurrentQueue
+
+Lists all items in the given concurrent queue.
+
+For simple types such as numbers, boolean and string, values are shown.
+> dcq 00000202a79320e8
+System.Collections.Concurrent.ConcurrentQueue<System.Int32>
+ 1 - 0
+ 2 - 1
+ 3 - 2
+
+In case of reference types, the command to dump each object is shown.
+> dcq 00000202a79337f8
+System.Collections.Concurrent.ConcurrentQueue<ForDump.ReferenceType>
+ 1 - dumpobj 0x202a7934e38
+ 2 - dumpobj 0x202a7934fd0
+ 3 - dumpobj 0x202a7935078
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "DumpConcurrentQueue" + Environment.NewLine +
- Environment.NewLine +
- "Lists all items in the given concurrent queue." + Environment.NewLine +
- Environment.NewLine +
- "For simple types such as numbers, boolean and string, values are shown." + Environment.NewLine +
- "> dcq 00000202a79320e8" + Environment.NewLine +
- "System.Collections.Concurrent.ConcurrentQueue<System.Int32>" + Environment.NewLine +
- " 1 - 0" + Environment.NewLine +
- " 2 - 1" + Environment.NewLine +
- " 3 - 2" + Environment.NewLine +
- Environment.NewLine +
- "In case of reference types, the command to dump each object is shown." + Environment.NewLine +
- "> dcq 00000202a79337f8" + Environment.NewLine +
- "System.Collections.Concurrent.ConcurrentQueue<ForDump.ReferenceType>" + Environment.NewLine +
- " 1 - dumpobj 0x202a7934e38" + Environment.NewLine +
- " 2 - dumpobj 0x202a7934fd0" + Environment.NewLine +
- " 3 - dumpobj 0x202a7935078" + Environment.NewLine +
- Environment.NewLine +
- "For value types, the command to dump each array segment is shown." + Environment.NewLine +
- "The next step is to manually dump each element with dumpvc <the Element Methodtable> <[item] address>." + Environment.NewLine +
- "> dcq 00000202a7933370" + Environment.NewLine +
- "System.Collections.Concurrent.ConcurrentQueue<ForDump.ValueType>" + Environment.NewLine +
- " 1 - dumparray 202a79334e0" + Environment.NewLine +
- " 2 - dumparray 202a7938a88" + Environment.NewLine +
- Environment.NewLine +
- ""
- ;
+For value types, the command to dump each array segment is shown.
+The next step is to manually dump each element with dumpvc <the Element Methodtable> <[item] address>.
+> dcq 00000202a7933370
+System.Collections.Concurrent.ConcurrentQueue<ForDump.ValueType>
+ 1 - dumparray 202a79334e0
+ 2 - dumparray 202a7938a88
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "dumpexceptions", Help = "Displays a list of all managed exceptions.")]
- public class DumpExceptionsCommand : CommandBase
+ public class DumpExceptionsCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; } = null!;
-
[ServiceImport]
public LiveObjectService LiveObjects { get; set; } = null!;
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "dumpgen", Aliases = new string[] { "dg" }, Help = "Displays heap content for the specified generation.")]
- public class DumpGenCommand : ExtensionCommandBase
+ public class DumpGenCommand : ClrMDHelperCommandBase
{
private const string statsHeader32bits = " MT Count TotalSize Class Name";
private const string statsHeader64bits = " MT Count TotalSize Class Name";
[Option(Name = "-mt", Help = "The address pointing on a Method table.")]
public string MethodTableAddress { get; set; }
- public override void ExtensionInvoke()
+ public override void Invoke()
{
GCGeneration generation = ParseGenerationArgument(Generation);
if (generation != GCGeneration.NotSet)
}
else
{
- WriteLine("Hexadecimal address expected for -mt option");
+ throw new DiagnosticsException("Hexadecimal address expected for -mt option");
}
}
WriteLine(string.Empty);
WriteLine($"Total {objectsCount} objects");
}
- private GCGeneration ParseGenerationArgument(string generation)
+ private static GCGeneration ParseGenerationArgument(string generation)
{
if (string.IsNullOrEmpty(generation))
{
- WriteLine("Generation argument is missing");
- return GCGeneration.NotSet;
+ throw new DiagnosticsException("Generation argument is missing");
}
string lowerString = generation.ToLowerInvariant();
GCGeneration result = lowerString switch
"foh" => GCGeneration.FrozenObjectHeap,
_ => GCGeneration.NotSet,
};
+
if (result == GCGeneration.NotSet)
{
- WriteLine($"{generation} is not a supported generation (gen0, gen1, gen2, loh, poh, foh)");
+ throw new DiagnosticsException($"{generation} is not a supported generation (gen0, gen1, gen2, loh, poh, foh)");
}
return result;
}
-
- protected override string GetDetailedHelp()
- {
- return
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
@"-------------------------------------------------------------------------------
DumpGen
This command can be used for 2 use cases:
00000184aa23e918 00007ff9ea6e75b8 40
Total 3 objects
";
- }
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "dumpheap", Help = "Displays a list of all managed objects.")]
- public class DumpHeapCommand : CommandBase
+ [Command(Name = "dumpheap", Aliases = new[] { "DumpHeap" }, Help = "Displays a list of all managed objects.")]
+ public class DumpHeapCommand : ClrRuntimeCommandBase
{
[ServiceImport]
public IMemoryService MemoryService { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public LiveObjectService LiveObjects { get; set; }
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Text;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.ExtensionCommands.Output;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "dumpobjgcrefs", Help = "A helper command to implement !dumpobj -refs")]
+ public sealed class DumpObjGCRefsCommand : ClrRuntimeCommandBase
+ {
+ private readonly StringBuilderPool _stringBuilderPool = new(260);
+
+ [Argument(Name = "object")]
+ public string ObjectAddress { get; set; }
+
+ public override void Invoke()
+ {
+ if (!TryParseAddress(ObjectAddress, out ulong objAddress))
+ {
+ throw new ArgumentException($"Invalid object address: '{ObjectAddress}'", nameof(ObjectAddress));
+ }
+
+ ClrObject obj = Runtime.Heap.GetObject(objAddress);
+ if (!obj.IsValid)
+ {
+ Console.WriteLine($"Unable to walk object references, invalid object.");
+ return;
+ }
+
+ ClrReference[] refs = obj.EnumerateReferencesWithFields(carefully: false, considerDependantHandles: false).ToArray();
+ if (refs.Length == 0)
+ {
+ Console.WriteLine("GC Refs: none");
+ return;
+ }
+
+ Console.WriteLine("GC Refs:");
+
+ Column fieldNameColumn = ColumnKind.Text.GetAppropriateWidth(refs.Select(r => GetFieldName(r)));
+ Column offsetName = ColumnKind.HexOffset.GetAppropriateWidth(refs.Select(r => r.Offset));
+
+ Table output = new(Console, fieldNameColumn, offsetName, ColumnKind.DumpObj, ColumnKind.TypeName);
+ output.WriteHeader("Field", "Offset", "Object", "Type");
+ foreach (ClrReference objRef in refs)
+ {
+ output.WriteRow(GetFieldName(objRef), objRef.Offset, objRef.Object, objRef.Object.Type);
+ }
+ }
+
+ private string GetFieldName(ClrReference objRef)
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+
+ if (objRef.Field is null)
+ {
+ return null;
+ }
+
+ if (objRef.InnerField is null)
+ {
+ return objRef.Field?.Name;
+ }
+
+ StringBuilder sb = _stringBuilderPool.Rent();
+ bool foundOneFieldName = false;
+
+ for (ClrReference? curr = objRef; curr.HasValue; curr = curr.Value.InnerField)
+ {
+ if (sb.Length > 0)
+ {
+ sb.Append('.');
+ }
+
+ string fieldName = curr.Value.Field?.Name;
+ if (string.IsNullOrWhiteSpace(fieldName))
+ {
+ sb.Append("???");
+ }
+ else
+ {
+ sb.Append(fieldName);
+ foundOneFieldName = true;
+ }
+ }
+
+ // Make sure we don't just return "???.???.???"
+ string result = foundOneFieldName ? sb.ToString() : null;
+ _stringBuilderPool.Return(sb);
+ return result;
+ }
+ }
+}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Linq;
-using System.Text;
-using Microsoft.Diagnostics.DebugServices;
-using Microsoft.Diagnostics.ExtensionCommands.Output;
-using Microsoft.Diagnostics.Runtime;
-
-namespace Microsoft.Diagnostics.ExtensionCommands
-{
- [Command(Name = "dumpobjgcrefs", Help = "A helper command to implement !dumpobj -refs")]
- public sealed class DumpObjGCRefsHelper : CommandBase
- {
- private readonly StringBuilderPool _stringBuilderPool = new(260);
-
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
- [Argument(Name = "object")]
- public string ObjectAddress { get; set; }
-
- public override void Invoke()
- {
- if (!TryParseAddress(ObjectAddress, out ulong objAddress))
- {
- throw new ArgumentException($"Invalid object address: '{ObjectAddress}'", nameof(ObjectAddress));
- }
-
- ClrObject obj = Runtime.Heap.GetObject(objAddress);
- if (!obj.IsValid)
- {
- Console.WriteLine($"Unable to walk object references, invalid object.");
- return;
- }
-
- ClrReference[] refs = obj.EnumerateReferencesWithFields(carefully: false, considerDependantHandles: false).ToArray();
- if (refs.Length == 0)
- {
- Console.WriteLine("GC Refs: none");
- return;
- }
-
- Console.WriteLine("GC Refs:");
-
- Column fieldNameColumn = ColumnKind.Text.GetAppropriateWidth(refs.Select(r => GetFieldName(r)));
- Column offsetName = ColumnKind.HexOffset.GetAppropriateWidth(refs.Select(r => r.Offset));
-
- Table output = new(Console, fieldNameColumn, offsetName, ColumnKind.DumpObj, ColumnKind.TypeName);
- output.WriteHeader("Field", "Offset", "Object", "Type");
- foreach (ClrReference objRef in refs)
- {
- output.WriteRow(GetFieldName(objRef), objRef.Offset, objRef.Object, objRef.Object.Type);
- }
- }
-
- private string GetFieldName(ClrReference objRef)
- {
- Console.CancellationToken.ThrowIfCancellationRequested();
-
- if (objRef.Field is null)
- {
- return null;
- }
-
- if (objRef.InnerField is null)
- {
- return objRef.Field?.Name;
- }
-
- StringBuilder sb = _stringBuilderPool.Rent();
- bool foundOneFieldName = false;
-
- for (ClrReference? curr = objRef; curr.HasValue; curr = curr.Value.InnerField)
- {
- if (sb.Length > 0)
- {
- sb.Append('.');
- }
-
- string fieldName = curr.Value.Field?.Name;
- if (string.IsNullOrWhiteSpace(fieldName))
- {
- sb.Append("???");
- }
- else
- {
- sb.Append(fieldName);
- foundOneFieldName = true;
- }
- }
-
- // Make sure we don't just return "???.???.???"
- string result = foundOneFieldName ? sb.ToString() : null;
- _stringBuilderPool.Return(sb);
- return result;
- }
- }
-}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "dumpruntimetypes", Help = "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too.")]
- public sealed class DumpRuntimeTypeCommand : CommandBase
+ [Command(Name = "dumpruntimetypes", Aliases = new[] { "DumpRuntimeTypes" }, Help = "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too.")]
+ public sealed class DumpRuntimeTypeCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
Table output = null;
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "dumpstackobjects", Aliases = new string[] { "dso" }, Help = "Displays all managed objects found within the bounds of the current stack.")]
- public class DumpStackObjectsCommand : CommandBase
+ [Command(Name = "dumpstackobjects", Aliases = new string[] { "dso", "DumpStackObjects" }, Help = "Displays all managed objects found within the bounds of the current stack.")]
+ public class DumpStackObjectsCommand : ClrRuntimeCommandBase
{
[ServiceImport]
public IMemoryService MemoryService { get; set; }
[ServiceImport]
public IThreadService ThreadService { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Option(Name = "-verify", Help = "Verify each object and only print ones that are valid objects.")]
public bool Verify { get; set; }
- [Argument(Name = "StackBounds", Help = "The top and bottom of the stack (in hex).")]
+ [Argument(Name = "stackbounds", Help = "The top and bottom of the stack (in hex).")]
public string[] Bounds { get; set; }
public override void Invoke()
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = CommandName, Help = "Displays information about native memory that CLR has allocated.")]
- public class EEHeapCommand : CommandBase
+ [Command(Name = CommandName, Aliases = new[] { "EEHeap" }, Help = "Displays information about native memory that CLR has allocated.")]
+ public class EEHeapCommand : ClrRuntimeCommandBase
{
private const string CommandName = "eeheap";
// Don't use the word "Total" if we have filtered out entries
private string TotalString => HeapWithFilters.HasFilters ? "Partial" : "Total";
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService MemoryService { get; set; }
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Diagnostics.DebugServices;
-
-namespace Microsoft.Diagnostics.ExtensionCommands
-{
- public abstract class ExtensionCommandBase : CommandBase
- {
- /// <summary>
- /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD.
- /// </summary>
- [ServiceImport(Optional = true)]
- public ClrMDHelper Helper { get; set; }
-
- public override void Invoke()
- {
- if (Helper == null)
- {
- throw new DiagnosticsException("No CLR runtime set");
- }
- ExtensionInvoke();
- }
-
- public abstract void ExtensionInvoke();
-
- [HelpInvoke]
- public void InvokeHelp()
- {
- WriteLine(GetDetailedHelp());
- }
-
- protected abstract string GetDetailedHelp();
- }
-}
internal static class ExtensionMethodHelpers
{
public static string ConvertToHumanReadable(this ulong totalBytes) => ConvertToHumanReadable((double)totalBytes);
+
public static string ConvertToHumanReadable(this long totalBytes) => ConvertToHumanReadable((double)totalBytes);
public static string ConvertToHumanReadable(this double totalBytes)
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "finalizequeue", Help = "Displays all objects registered for finalization.")]
- public class FinalizeQueueCommand : CommandBase
+ [Command(Name = "finalizequeue", Aliases = new[] { "fq", "FinalizeQueue" }, Help = "Displays all objects registered for finalization.")]
+ public class FinalizeQueueCommand : ClrRuntimeCommandBase
{
[Option(Name = "-detail", Help = "Will display extra information on any SyncBlocks that need to be cleaned up, and on any RuntimeCallableWrappers (RCWs) that await cleanup. Both of these data structures are cached and cleaned up by the finalizer thread when it gets a chance to run.")]
public bool Detail { get; set; }
[ServiceImport]
public DumpHeapService DumpHeap { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
ulong mt = 0;
DumpHeap.PrintHeap(objects, displayKind, Stat, printFragmentation: false);
}
+
private IEnumerable<ClrObject> EnumerateFinalizableObjects(bool allReady, ulong mt)
{
IEnumerable<ClrObject> result = EnumerateValidFinalizableObjectsWithTypeFilter(mt);
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "ephtoloh", Help = "Finds ephemeral objects which reference the large object heap.")]
- public class FindEphemeralReferencesToLOHCommand : CommandBase
+ public class FindEphemeralReferencesToLOHCommand : ClrRuntimeCommandBase
{
// IComparer for binary search
private readonly IComparer<(ClrObject, ClrObject)> _firstObjectComparer = Comparer<(ClrObject, ClrObject)>.Create((x, y) => x.Item1.Address.CompareTo(y.Item1.Address));
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
int segments = Runtime.Heap.Segments.Count(seg => seg.Kind is not GCSegmentKind.Frozen or GCSegmentKind.Pinned);
PrintPointers(!ShowAllObjects, Regions);
}
+ [FilterInvoke(Message = "The memory region service does not exists. This command is only supported under windbg/cdb debuggers.")]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime, [ServiceImport(Optional = true)] NativeAddressHelper helper) => runtime != null && helper != null;
+
private void PrintPointers(bool pinnedOnly, params string[] memTypes)
{
DescribedRegion[] allRegions = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false, includeHandleTableIfSlow: false).ToArray();
}
[HelpInvoke]
- public void HelpInvoke()
- {
- WriteLine(
+ public static string GetDetailedHelp() =>
@"-------------------------------------------------------------------------------
The findpointersin command will search the regions of memory given by MADDRESS_TYPE_LIST
to find all pointers to other memory regions and display them. By default, pointers
then this command print out ALL objects that are pointed to instead of collapsing
them into one entry.
-usage: !findpointersin [--all] MADDRESS_TYPE_LIST
+usage: findpointersin [--all] MADDRESS_TYPE_LIST
-Note: The MADDRESS_TYPE_LIST must be a memory type as printed by !maddress.
+Note: The MADDRESS_TYPE_LIST must be a memory type as printed by maddress.
-Example: ""!findpointersin PAGE_READWRITE"" will only search for regions of memory that
+Example: ""findpointersin PAGE_READWRITE"" will only search for regions of memory that
!maddress marks as ""PAGE_READWRITE"" and not every page of memory that's
marked with PAGE_READWRITE protection.
-Example: Running the command ""!findpointersin Stack PAGE_READWRITE"" will find all pointers
+Example: Running the command ""findpointersin Stack PAGE_READWRITE"" will find all pointers
on any ""Stack"" and ""PAGE_READWRITE"" memory segments and summarize those contents into
three tables: One table for pointers to the GC heap, one table for pointers where
symbols could be resolved, and one table of pointers where we couldn't resolve symbols.
...
--------------------------------------------------------- [ TOTALS ] ---------33,360---------72,029---------------
-");
- }
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "ephrefs", Help = "Finds older generation objects which reference objects in the ephemeral segment.")]
- public class FindReferencesToEphemeralCommand : CommandBase
+ public class FindReferencesToEphemeralCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
private readonly HashSet<ulong> _referenced = new();
private ulong _referencedSize;
Console.WriteLine($"{objCount:n0} older generation objects referenced {_referenced.Count:n0} younger objects ({_referencedSize:n0} bytes)");
}
-
private IEnumerable<EphemeralRefCount> FindObjectsWithEphemeralReferences()
{
foreach (ClrSegment seg in Runtime.Heap.Segments)
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "gcheapstat", DefaultOptions = "GCHeapStat", Help = "Displays various GC heap stats.")]
- public class GCHeapStatCommand : CommandBase
+ [Command(Name = "gcheapstat", Aliases = new[] { "GCHeapStat" }, Help = "Displays various GC heap stats.")]
+ public class GCHeapStatCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public LiveObjectService LiveObjects { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "gcroot", Help = "Displays info about references (or roots) to an object at the specified address.")]
- public class GCRootCommand : CommandBase
+ [Command(Name = "gcroot", Aliases = new[] { "GCRoot" }, Help = "Displays info about references (or roots) to an object at the specified address.")]
+ public class GCRootCommand : ClrRuntimeCommandBase
{
private StringBuilder _lineBuilder = new(64);
private ClrRoot _lastRoot;
[ServiceImport]
public IMemoryService Memory { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public RootCacheService RootCache { get; set; }
ClrSegment seg = Runtime.Heap.GetSegmentByAddress(address);
if (seg is null)
{
- Console.WriteLineError($"Address {address:x} is not in the managed heap.");
- return;
+ throw new DiagnosticsException($"Address {address:x} is not in the managed heap.");
}
Generation objectGen = seg.GetGeneration(address);
public bool ShowAll { get; set; }
[ServiceImport]
- public ClrRuntime Runtime { get; set; }
+ public NativeAddressHelper AddressHelper { get; set; }
[ServiceImport]
- public NativeAddressHelper AddressHelper { get; set; }
+ public ClrRuntime Runtime { get; set; }
private int Width
{
PrintGCPointersToMemory(ShowAll, MemoryTypes);
}
+ [FilterInvoke(Message = "The memory region service does not exists. This command is only supported under windbg/cdb debuggers.")]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] ClrRuntime runtime, [ServiceImport(Optional = true)] NativeAddressHelper helper) => runtime != null && helper != null;
+
public void PrintGCPointersToMemory(bool showAll, params string[] memoryTypes)
{
// Strategy:
}
[HelpInvoke]
- public void HelpInvoke()
- {
- WriteLine(
+ public static string GetDetailedHelp() =>
@"-------------------------------------------------------------------------------
-!gctonative searches the GC heap for pointers to native memory. This is used
+gctonative searches the GC heap for pointers to native memory. This is used
to help locate regions of native memory that are referenced (or possibly held
alive) by objects on the GC heap.
-usage: !gctonative [--all] MADDRESS_TYPE_LIST
+usage: gctonative [--all] MADDRESS_TYPE_LIST
-Note: The MADDRESS_TYPE_LIST must be a memory type as printed by !maddress.
+Note: The MADDRESS_TYPE_LIST must be a memory type as printed by maddress.
If --all is set, a full list of every pointer from the GC heap to the
specified memory will be displayed instead of just a summary table.
Sample Output:
- 0:000> !gctonative PAGE_READWRITE
+ 0:000> gctonative PAGE_READWRITE
Walking GC heap to find pointers...
Resolving object names...
================================================ PAGE_READWRITE Regions ================================================
System.Net.Sockets.SocketAsyncEngine | 1 | 7f059800edd0
Microsoft.Extensions.Caching.Memory.CacheEntry | 1 | 7f05241e0000
System.Runtime.CompilerServices.AsyncTaskMethodBuilder<...>+AsyncStateMachine... | 1 | 7f0500000004
-");
- }
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "gcwhere", Help = "Displays the location in the GC heap of the specified address.")]
- public class GCWhereCommand : CommandBase
+ [Command(Name = "gcwhere", Aliases = new[] { "GCWhere" }, Help = "Displays the location in the GC heap of the specified address.")]
+ public class GCWhereCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService MemoryService { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "logopen", Help = "Enables console file logging.", Flags = CommandFlags.Global)]
- [Command(Name = "logclose", DefaultOptions = "--disable", Help = "Disables console file logging.", Flags = CommandFlags.Global)]
+ [Command(Name = "logopen", Help = "Enables console file logging.")]
+ [Command(Name = "logclose", DefaultOptions = "--disable", Help = "Disables console file logging.")]
public class ConsoleLoggingCommand : CommandBase
{
[ServiceImport(Optional = true)]
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Generic;
+using System.Linq;
using Microsoft.Diagnostics.DebugServices;
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "help", Help = "Displays help for a command.", Flags = CommandFlags.Global)]
+ [Command(Name = "help", Aliases = new string[] { "soshelp" }, Help = "Displays help for a command.")]
public class HelpCommand : CommandBase
{
[Argument(Help = "Command to find help.")]
public override void Invoke()
{
- if (!CommandService.DisplayHelp(Command, Services))
+ if (string.IsNullOrWhiteSpace(Command))
{
- throw new NotSupportedException($"Help for {Command} not found");
+ IEnumerable<(string Invocation, string Help)> commands = CommandService.GetAllCommandHelp(Services);
+ int invocationWidth = commands.Max((item) => item.Invocation.Length) + 4;
+
+ Write(string.Concat(commands.
+ OrderBy(item => item.Invocation, StringComparer.OrdinalIgnoreCase).
+ Select((item) => $"{FormatInvocation(item.Invocation)}{item.Help}{Environment.NewLine}")));
+
+ string FormatInvocation(string invocation) => invocation + new string(' ', invocationWidth - invocation.Length);
+ }
+ else
+ {
+ string helpText = CommandService.GetDetailedHelp(Command, Services, Console.WindowWidth) ?? throw new DiagnosticsException($"Help for {Command} not found");
+ Write(helpText);
}
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "logging", Help = "Enables/disables internal diagnostic logging.", Flags = CommandFlags.Global)]
+ [Command(Name = "logging", Help = "Enables/disables internal diagnostic logging.")]
public class LoggingCommand : CommandBase
{
[ServiceImport(Optional = true)]
[ServiceImport]
public IThreadService ThreadService { get; set; }
- [ServiceImport]
+ [ServiceImport(Optional = true)]
public IThread CurrentThread { get; set; }
[Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")]
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(
- Name = "setsymbolserver",
- Aliases = new string[] { "SetSymbolServer" },
- Help = "Enables and sets symbol server support for symbols and module download.",
- Flags = CommandFlags.Global)]
- [Command(
- Name = "loadsymbols",
- DefaultOptions = "--loadsymbols",
- Help = "Loads symbols for all modules.",
- Flags = CommandFlags.Global)]
+ [Command(Name = "setsymbolserver", Aliases = new string[] { "SetSymbolServer" }, Help = "Enables and sets symbol server support for symbols and module download.")]
+ [Command(Name = "loadsymbols", DefaultOptions = "--loadsymbols", Help = "Loads symbols for all modules.")]
public class SetSymbolServerCommand : CommandBase
{
[ServiceImport]
Write(SymbolService.ToString());
}
}
+
+ [HelpInvoke]
+ public static string GetDetailedHelp(IHost host)
+ {
+ switch (host.HostType)
+ {
+ case HostType.DbgEng:
+ return s_detailedHelpTextDbgEng;
+ case HostType.Lldb:
+ return s_detailedHelpTextLLDB;
+ case HostType.DotnetDump:
+ return s_detailedHelpTextDotNetDump;
+ }
+ return null;
+ }
+
+ private const string s_detailedHelpTextDbgEng =
+ @"
+This commands enables symbol server support for portable PDBs for managed assemblies and
+.NET Core native modules files (like the DAC) in SOS. If the .sympath is set, the symbol
+server supported is automatically set and this command isn't necessary.
+";
+
+ private const string s_detailedHelpTextLLDB =
+ @"
+This commands enables symbol server support in SOS. The portable PDBs for managed assemblies
+and .NET Core native symbol and module (like the DAC) files are downloaded.
+
+To enable downloading symbols from the Microsoft symbol server:
+
+ (lldb) setsymbolserver -ms
+
+This command may take some time without any output while it attempts to download the symbol files.
+
+To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set:
+
+ (lldb) setsymbolserver -disable
+
+To add a directory to search for symbols:
+
+ (lldb) setsymbolserver -directory /home/mikem/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.
+
+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
+runtime with the ""sethostruntime"" command. Type ""soshelp sethostruntime"" for more details.
+
+ (lldb) setsymbolserver -ms
+ Error: Fail to initialize CoreCLR 80004005
+ SetSymbolServer -ms failed
+
+The ""-loadsymbols"" option and the ""loadsymbol"" command alias attempts to download the native .NET
+Core symbol files. It is only useful for live sessions and not core dumps. This command needs to
+be run before the lldb ""bt"" (stack trace) or the ""clrstack -f"" (dumps both managed and native
+stack frames).
+
+ (lldb) loadsymbols
+ (lldb) bt
+";
+
+ private const string s_detailedHelpTextDotNetDump =
+ @"
+This commands enables symbol server support in SOS. The portable PDBs for managed assemblies
+and .NET Core native module (like the DAC) files are downloaded.
+
+To enable downloading symbols from the Microsoft symbol server:
+
+ > setsymbolserver -ms
+
+This command may take some time without any output while it attempts to download the symbol files.
+
+To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set:
+
+ > setsymbolserver -disable
+
+To add a directory to search for symbols:
+
+ > setsymbolserver -directory /home/mikem/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.
+
+To clear the default cache run ""rm -r $HOME/.dotnet/symbolcache"" in a command shell.
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "listnearobj", Help = "Displays the object preceding and succeeding the specified address.")]
- public class ListNearObjCommand : CommandBase
+ [Command(Name = "listnearobj", Aliases = new[] { "lno", "ListNearObj" }, Help = "Displays the object preceding and succeeding the specified address.")]
+ public class ListNearObjCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService MemoryService { get; set; }
}
}
+ [FilterInvoke(Message = "The memory region service does not exists. This command is only supported under windbg/cdb debuggers.")]
+ public static bool FilterInvoke([ServiceImport(Optional = true)] NativeAddressHelper helper) => helper != null;
+
[HelpInvoke]
- public void HelpInvoke()
- {
- WriteLine(
+ public static string GetDetailedHelp() =>
$@"-------------------------------------------------------------------------------
-maddress is a managed version of !address, which attempts to annotate all memory
+!maddress is a managed version of !address, which attempts to annotate all memory
with information about CLR's heaps.
-usage: !sos maddress [{SummaryFlag}] [{ImagesFlag}] [{ForceHandleTableFlag}] [{ReserveFlag} [{ReserveHeuristicFlag}]]
+usage: !maddress [{SummaryFlag}] [{ImagesFlag}] [{ForceHandleTableFlag}] [{ReserveFlag} [{ReserveHeuristicFlag}]]
Flags:
{SummaryFlag}
{BySizeFlag}
Order the list of memory blocks by size (descending) when printing the list
of all memory blocks instead of by address.
-");
- }
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [ServiceExport(Scope = ServiceScope.Target)]
public sealed class NativeAddressHelper : IDisposable
{
private readonly IDisposable _onFlushEvent;
private ((bool, bool, bool, bool) Key, DescribedRegion[] Result) _previous;
- public NativeAddressHelper(ITarget target)
+ [ServiceExport(Scope = ServiceScope.Target)]
+ public static NativeAddressHelper TryCreate(ITarget target, [ServiceImport(Optional = true)] IMemoryRegionService memoryRegionService)
+ {
+ return memoryRegionService != null ? new NativeAddressHelper(target, memoryRegionService) : null;
+ }
+
+ private NativeAddressHelper(ITarget target, IMemoryRegionService memoryRegionService)
{
Target = target;
+ MemoryRegionService = memoryRegionService;
_onFlushEvent = target.OnFlushEvent.Register(() => _previous = default);
}
public void Dispose() => _onFlushEvent.Dispose();
- [ServiceImport]
- public ITarget Target { get; set; }
+ public ITarget Target { get; }
+
+ public IMemoryRegionService MemoryRegionService { get; }
[ServiceImport]
public IMemoryService MemoryService { get; set; }
[ServiceImport]
public IModuleService ModuleService { get; set; }
- [ServiceImport]
- public IMemoryRegionService MemoryRegionService { get; set; }
-
[ServiceImport]
public IConsoleService Console { get; set; }
foreach (IRuntime runtime in RuntimeService.EnumerateRuntimes())
{
ClrRuntime clrRuntime = runtime.Services.GetService<ClrRuntime>();
- RootCacheService rootCache = runtime.Services.GetService<RootCacheService>();
if (clrRuntime is not null)
{
+ RootCacheService rootCache = runtime.Services.GetService<RootCacheService>() ?? throw new DiagnosticsException("NativeAddressHelper: RootCacheService not found");
foreach ((ulong Address, ulong Size, ClrMemoryKind Kind) mem in EnumerateClrMemoryAddresses(clrRuntime, rootCache, includeHandleTableIfSlow))
{
// The GCBookkeeping range is a large region of memory that the GC reserved. We'll simply mark every
/// Prints objects and statistics for a range of object pointers.
/// </summary>
[Command(Name = "notreachableinrange", Help = "A helper command for !finalizerqueue")]
- public class NotReachableInRangeCommand : CommandBase
+ public class NotReachableInRangeCommand : ClrRuntimeCommandBase
{
private HashSet<ulong> _nonFQLiveObjects;
[ServiceImport]
public IMemoryService Memory { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Option(Name = "-short")]
public bool Short { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "objsize", Help = "Lists the sizes of the all the objects found on managed threads.")]
- public class ObjSizeCommand : CommandBase
+ [Command(Name = "objsize", Aliases = new[] { "ObjSize" }, Help = "Lists the sizes of the all the objects found on managed threads.")]
+ public class ObjSizeCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public DumpHeapService DumpHeap { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "parallelstacks", Aliases = new string[] { "pstacks" }, Help = "Displays the merged threads stack similarly to the Visual Studio 'Parallel Stacks' panel.")]
- public class ParallelStacksCommand : ExtensionCommandBase
+ public class ParallelStacksCommand : ClrMDHelperCommandBase
{
- [ServiceImport]
+ [ServiceImport(Optional = true)]
public ClrRuntime Runtime { get; set; }
[Option(Name = "--allthreads", Aliases = new string[] { "-a" }, Help = "Displays all threads per group instead of at most 4 by default.")]
public bool AllThreads { get; set; }
- public override void ExtensionInvoke()
+ public override void Invoke()
{
- ParallelStack ps = ParallelStacks.Runtime.ParallelStack.Build(Runtime);
+ ParallelStack ps = ParallelStack.Build(Runtime);
if (ps == null)
{
return;
WriteLine($"==> {ps.ThreadIds.Count} threads with {ps.Stacks.Count} roots{Environment.NewLine}");
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+ParallelStacks
+
+pstacks groups the callstack of all running threads and shows a merged display a la Visual Studio 'Parallel Stacks' panel
+By default, only 4 threads ID per frame group are listed. Use --allThreads/-a to list all threads ID.
+
+> pstacks
+________________________________________________
+~~~~ 8f8c
+ 1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
+ ...
+ 1 System.Console.ReadLine()
+ 1 NetCoreConsoleApp.Program.Main(String[])
+
+________________________________________________
+ ~~~~ 7034
+ 1 System.Threading.Monitor.Wait(Object, Int32, Boolean)
+ ...
+ 1 System.Threading.Tasks.Task.Wait()
+ 1 NetCoreConsoleApp.Program+c.b__1_4(Object)
+ ~~~~ 9c6c,4020
+ 2 System.Threading.Monitor.Wait(Object, Int32, Boolean)
+ ...
+ 2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7()
+ 3 System.Threading.Tasks.Task.InnerInvoke()
+ 4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object)
+ ...
+ 4 System.Threading.Tasks.Task.ExecuteEntryUnsafe()
+ 4 System.Threading.Tasks.Task.ExecuteWorkItem()
+ 7 System.Threading.ThreadPoolWorkQueue.Dispatch()
+ 7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "ParallelStacks" + Environment.NewLine +
- Environment.NewLine +
- "pstacks groups the callstack of all running threads and shows a merged display a la Visual Studio 'Parallel Stacks' panel" + Environment.NewLine +
- "By default, only 4 threads ID per frame group are listed. Use --allThreads/-a to list all threads ID." + Environment.NewLine +
- Environment.NewLine +
- "> pstacks" + Environment.NewLine +
- "________________________________________________" + Environment.NewLine +
- "~~~~ 8f8c" + Environment.NewLine +
- " 1 (dynamicClass).IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 1 System.Console.ReadLine()" + Environment.NewLine +
- " 1 NetCoreConsoleApp.Program.Main(String[])" + Environment.NewLine +
- Environment.NewLine +
- "________________________________________________" + Environment.NewLine +
- " ~~~~ 7034" + Environment.NewLine +
- " 1 System.Threading.Monitor.Wait(Object, Int32, Boolean)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 1 System.Threading.Tasks.Task.Wait()" + Environment.NewLine +
- " 1 NetCoreConsoleApp.Program+c.b__1_4(Object)" + Environment.NewLine +
- " ~~~~ 9c6c,4020" + Environment.NewLine +
- " 2 System.Threading.Monitor.Wait(Object, Int32, Boolean)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 2 NetCoreConsoleApp.Program+c__DisplayClass1_0.b__7()" + Environment.NewLine +
- " 3 System.Threading.Tasks.Task.InnerInvoke()" + Environment.NewLine +
- " 4 System.Threading.Tasks.Task+c.cctor>b__278_1(Object)" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 4 System.Threading.Tasks.Task.ExecuteEntryUnsafe()" + Environment.NewLine +
- " 4 System.Threading.Tasks.Task.ExecuteWorkItem()" + Environment.NewLine +
- " 7 System.Threading.ThreadPoolWorkQueue.Dispatch()" + Environment.NewLine +
- " 7 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()" + Environment.NewLine +
- Environment.NewLine +
- "==> 8 threads with 2 roots" + Environment.NewLine +
- Environment.NewLine +
- ""
- ;
+==> 8 threads with 2 roots
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name ="pathto", Help = "Displays the GC path from <root> to <target>.")]
- public class PathToCommand : CommandBase
+ [Command(Name ="pathto", Aliases = new[] { "PathTo" }, Help = "Displays the GC path from <root> to <target>.")]
+ public class PathToCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public RootCacheService RootCache { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
[DebugCommand(Name=nameof(SimulateGCHeapCorruption), Help = "Writes values to the GC heap in strategic places to simulate heap corruption.")]
- public class SimulateGCHeapCorruption : CommandBase
+ public class SimulateGCHeapCorruption : ClrRuntimeCommandBase
{
private static readonly List<Change> _changes = new();
[ServiceImport]
public IMemoryService MemoryService { get; set; }
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Argument]
public string Command { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "sizestats", Help = "Size statistics for the GC heap.")]
- public sealed class SizeStatsCommand : CommandBase
+ public sealed class SizeStatsCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
public override void Invoke()
{
SizeStats(Generation.Generation0, isFree: false);
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "taskstate", Aliases = new string[] { "tks" }, Help = "Displays a Task state in a human readable format.")]
- public class TaskStateCommand : ExtensionCommandBase
+ public class TaskStateCommand : ClrMDHelperCommandBase
{
[Argument(Help = "The Task instance address.")]
public string Address { get; set; }
[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 ExtensionInvoke()
+ public override void Invoke()
{
if (string.IsNullOrEmpty(Address) && !Value.HasValue)
{
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+TaskState [hexa address] [-v <decimal state value>]
+
+TaskState translates a Task m_stateFlags field value into human readable format.
+It supports hexadecimal address corresponding to a task instance or -v <decimal state value>.
+
+> tks 000001db16cf98f0
+Running
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "TaskState [hexa address] [-v <decimal state value>]" + Environment.NewLine +
- Environment.NewLine +
- "TaskState translates a Task m_stateFlags field value into human readable format." + Environment.NewLine +
- "It supports hexadecimal address corresponding to a task instance or -v <decimal state value>." + Environment.NewLine +
- Environment.NewLine +
- "> tks 000001db16cf98f0" + Environment.NewLine +
- "Running" + Environment.NewLine +
- Environment.NewLine +
- "> tks -v 73728" + Environment.NewLine +
- "WaitingToRun"
- ;
+> tks -v 73728
+WaitingToRun
+";
}
}
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "threadpool", Help = "Displays info about the runtime thread pool.")]
- public sealed class ThreadPoolCommand : CommandBase
+ [Command(Name = "threadpool", Aliases = new[] { "ThreadPool" }, Help = "Displays info about the runtime thread pool.")]
+ public sealed class ThreadPoolCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[Option(Name = "-ti", Help = "Print the hill climbing log.", Aliases = new string[] { "-hc" })]
public bool PrintHillClimbingLog { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "threadpoolqueue", Aliases = new string[] { "tpq" }, Help = "Displays queued ThreadPool work items.")]
- public class ThreadPoolQueueCommand : ExtensionCommandBase
+ public class ThreadPoolQueueCommand : ClrMDHelperCommandBase
{
- public override void ExtensionInvoke()
+ public override void Invoke()
{
Dictionary<string, WorkInfo> workItems = new();
int workItemCount = 0;
wi.Count++;
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+ThreadPoolQueue
+
+ThreadPoolQueue lists the enqueued work items in the Clr Thread Pool followed by a summary of the different tasks/work items.
+The global queue is first iterated before local per-thread queues.
+The name of the method to be called (on which instance if any) is also provided when available.
+
+> tpq
+
+global work item queue________________________________
+0x000002AC3C1DDBB0 Work | (ASP.global_asax)System.Web.HttpApplication.ResumeStepsWaitCallback
+ ...
+0x000002AABEC19148 Task | System.Threading.Tasks.Dataflow.Internal.TargetCore<System.Action>.<ProcessAsyncIfNecessary_Slow>b__3
+
+local per thread work items_____________________________________
+0x000002AE79D80A00 System.Threading.Tasks.ContinuationTaskFromTask
+ ...
+0x000002AB7CBB84A0 Task | System.Net.Http.HttpClientHandler.StartRequest
+
+ 7 Task System.Threading.Tasks.Dataflow.Internal.TargetCore<System.Action>.<ProcessAsyncIfNecessary_Slow>b__3
+ ...
+ 84 Task System.Net.Http.HttpClientHandler.StartRequest
+----
+6039
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "ThreadPoolQueue" + Environment.NewLine +
- Environment.NewLine +
- "ThreadPoolQueue lists the enqueued work items in the Clr Thread Pool followed by a summary of the different tasks/work items." + Environment.NewLine +
- "The global queue is first iterated before local per-thread queues." + Environment.NewLine +
- "The name of the method to be called (on which instance if any) is also provided when available." + Environment.NewLine +
- Environment.NewLine +
- "> tpq" + Environment.NewLine +
- Environment.NewLine +
- "global work item queue________________________________" + Environment.NewLine +
- "0x000002AC3C1DDBB0 Work | (ASP.global_asax)System.Web.HttpApplication.ResumeStepsWaitCallback" + Environment.NewLine +
- " ..." + Environment.NewLine +
- "0x000002AABEC19148 Task | System.Threading.Tasks.Dataflow.Internal.TargetCore<System.Action>.<ProcessAsyncIfNecessary_Slow>b__3" + Environment.NewLine +
- "" + Environment.NewLine +
- "local per thread work items_____________________________________" + Environment.NewLine +
- "0x000002AE79D80A00 System.Threading.Tasks.ContinuationTaskFromTask" + Environment.NewLine +
- " ..." + Environment.NewLine +
- "0x000002AB7CBB84A0 Task | System.Net.Http.HttpClientHandler.StartRequest" + Environment.NewLine +
- "" + Environment.NewLine +
- " 7 Task System.Threading.Tasks.Dataflow.Internal.TargetCore<System.Action>.<ProcessAsyncIfNecessary_Slow>b__3" + Environment.NewLine +
- " ..." + Environment.NewLine +
- " 84 Task System.Net.Http.HttpClientHandler.StartRequest" + Environment.NewLine +
- "----" + Environment.NewLine +
- "6039" + Environment.NewLine +
- "" + Environment.NewLine +
- "1810 Work (ASP.global_asax) System.Web.HttpApplication.ResumeStepsWaitCallback" + Environment.NewLine +
- "----" + Environment.NewLine +
- "1810" + Environment.NewLine +
- ""
- ;
+1810 Work (ASP.global_asax) System.Web.HttpApplication.ResumeStepsWaitCallback
+----
+1810";
private sealed class WorkInfo
{
namespace Microsoft.Diagnostics.ExtensionCommands
{
[Command(Name = "timerinfo", Aliases = new string[] { "ti" }, Help = "Displays information about running timers.")]
- public class TimersCommand : ExtensionCommandBase
+ public class TimersCommand : ClrMDHelperCommandBase
{
- public override void ExtensionInvoke()
+ public override void Invoke()
{
try
{
}
- protected override string GetDetailedHelp()
- {
- return DetailedHelpText;
- }
+ [HelpInvoke]
+ public static string GetDetailedHelp() =>
+@"-------------------------------------------------------------------------------
+TimerInfo
- private readonly string DetailedHelpText =
- "-------------------------------------------------------------------------------" + Environment.NewLine +
- "TimerInfo" + Environment.NewLine +
- Environment.NewLine +
- "TimerInfo lists all the running timers followed by a summary of the different items." + Environment.NewLine +
- "The name of the method to be called (on which instance if any) is also provided when available." + Environment.NewLine +
- Environment.NewLine +
- "> ti" + Environment.NewLine +
- "0x000001E29BD45848 @ 964 ms every 1000 ms | 0x000001E29BD0C828 (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->" + Environment.NewLine +
- "0x000001E19BD0F868 @ 1 ms every ------ ms | 0x000001E19BD0F800 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1" + Environment.NewLine +
- "0x000001E09BD09B40 @ 1 ms every ------ ms | 0x000001E09BD09AD8 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1" + Environment.NewLine +
- "0x000001E29BD58C68 @ 1 ms every ------ ms | 0x000001E29BD58C00 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1" + Environment.NewLine +
- "0x000001E29BCB1398 @ 5000 ms every ------ ms | 0x0000000000000000 () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand" + Environment.NewLine +
- Environment.NewLine +
- " 5 timers" + Environment.NewLine +
- "-----------------------------------------------" + Environment.NewLine +
- " 1 | @ 964 ms every 1000 ms | (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->" + Environment.NewLine +
- " 1 | @ 5000 ms every ------ ms | () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand" + Environment.NewLine +
- " 3 | @ 1 ms every ------ ms | (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1"
- ;
- }
+TimerInfo lists all the running timers followed by a summary of the different items.
+The name of the method to be called (on which instance if any) is also provided when available.
+> ti
+0x000001E29BD45848 @ 964 ms every 1000 ms | 0x000001E29BD0C828 (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->
+0x000001E19BD0F868 @ 1 ms every ------ ms | 0x000001E19BD0F800 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1
+0x000001E09BD09B40 @ 1 ms every ------ ms | 0x000001E09BD09AD8 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1
+0x000001E29BD58C68 @ 1 ms every ------ ms | 0x000001E29BD58C00 (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1
+0x000001E29BCB1398 @ 5000 ms every ------ ms | 0x0000000000000000 () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand
+
+ 5 timers
+-----------------------------------------------
+ 1 | @ 964 ms every 1000 ms | (Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.Heartbeat) ->
+ 1 | @ 5000 ms every ------ ms | () -> System.Diagnostics.Tracing.EventPipeController.PollForTracingCommand
+ 3 | @ 1 ms every ------ ms | (System.Threading.Tasks.Task+DelayPromise) -> System.Threading.Tasks.Task+<>c.<Delay>b__260_1
+";
+ }
internal sealed class TimerStat
{
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "traverseheap", Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")]
- public class TraverseHeapCommand : CommandBase
+ [Command(Name = "traverseheap", Aliases = new[] { "TraverseHeap" }, Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")]
+ public class TraverseHeapCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public RootCacheService RootCache { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = CommandName, Help = "Searches the managed heap for memory corruption..")]
- public class VerifyHeapCommand : CommandBase
+ [Command(Name = CommandName, Aliases = new[] { "VerifyHeap" }, Help = "Searches the managed heap for memory corruption..")]
+ public class VerifyHeapCommand : ClrRuntimeCommandBase
{
private const string CommandName = "verifyheap";
private int _totalObjects;
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService MemoryService { get; set; }
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "verifyobj", Help = "Checks the given object for signs of corruption.")]
- public sealed class VerifyObjectCommand : CommandBase
+ [Command(Name = "verifyobj", Aliases = new[] { "VerifyObj" }, Help = "Checks the given object for signs of corruption.")]
+ public sealed class VerifyObjectCommand : ClrRuntimeCommandBase
{
- [ServiceImport]
- public ClrRuntime Runtime { get; set; }
-
[ServiceImport]
public IMemoryService Memory { get; set; }
- [Argument(Name = "ObjectAddress", Help = "The object to verify.")]
+ [Argument(Name = "objectaddress", Help = "The object to verify.")]
public string ObjectAddress { get; set; }
public override void Invoke()
}
catch (Exception ex)
{
- // Most exceptions should not excape the command dispatch, but just in case
- WriteLine(OutputType.Error, "ERROR: {0}", ex.Message);
+ if (!string.IsNullOrEmpty(ex.Message))
+ {
+ WriteLine(OutputType.Error, "ERROR: {0}", ex.Message);
+ }
+ if (ex is CommandParsingException parsingException)
+ {
+ WriteLine(OutputType.Normal, parsingException.DetailedHelp);
+ }
Trace.TraceError(ex.ToString());
m_lastCommandLine = null;
result = false;
namespace Microsoft.Diagnostics.Repl
{
- [Command(Name = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exits interactive mode.", Flags = CommandFlags.Global | CommandFlags.Manual)]
+ [Command(Name = "exit", Aliases = new string[] { "quit", "q" }, Help = "Exits interactive mode.")]
public class ExitCommand : CommandBase
{
private readonly Action _exit;
_symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
}
+ public override void Dispose()
+ {
+ base.Dispose();
+ _dataTarget?.Dispose();
+ _dataTarget = null;
+ }
+
+ public ServiceManager ServiceManager => _serviceManager;
+
+ public ServiceContainer ServiceContainer => _serviceContainer;
+
protected override ITarget GetTarget()
{
_dataTarget = DataTarget.LoadDump(DumpFile);
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
using Microsoft.Diagnostics.DebugServices;
namespace Microsoft.Diagnostics.TestHelpers
{
- public abstract class TestHost
+ public abstract class TestHost : IDisposable
{
private TestDataReader _testData;
private ITarget _target;
Config = config;
}
+ public virtual void Dispose()
+ {
+ _target?.Destroy();
+ _target = null;
+ }
+
public TestDataReader TestData
{
get
using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
using Microsoft.Diagnostics.Runtime.Utilities;
+using SOS.Hosting;
using SOS.Hosting.DbgEng.Interop;
namespace SOS.Extensions
{
- internal sealed unsafe class DebuggerServices : CallableCOMWrapper
+ internal sealed unsafe class DebuggerServices : CallableCOMWrapper, SOSHost.INativeClient
{
internal enum OperatingSystem
{
: base(new RefCountedFreeLibrary(IntPtr.Zero), IID_IDebuggerServices, punk)
{
_hostType = hostType;
+ Client = punk;
// This uses COM marshalling code, so we also check that the OSPlatform is Windows.
if (hostType == HostType.DbgEng && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
}
}
+ #region INativeClient
+
+ public IntPtr Client { get; }
+
+ #endregion
+
public HResult GetOperatingSystem(out OperatingSystem operatingSystem)
{
return VTable.GetOperatingSystem(Self, out operatingSystem);
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
-using System.Text;
using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.DebugServices.Implementation;
using Microsoft.Diagnostics.ExtensionCommands;
/// <summary>
/// The extension services Wrapper the native hosts are given
/// </summary>
- public sealed unsafe class HostServices : COMCallableIUnknown, IHost
+ public sealed unsafe class HostServices : COMCallableIUnknown, IHost, SOSLibrary.ISOSModule
{
private static readonly Guid IID_IHostServices = new("27B2CB8D-BDEE-4CBD-B6EF-75880D76D46F");
private readonly SymbolService _symbolService;
private readonly HostWrapper _hostWrapper;
private ServiceContainer _serviceContainer;
+ private ServiceContainer _servicesWithManagedOnlyFilter;
private ContextServiceFromDebuggerServices _contextService;
private int _targetIdFactory;
private ITarget _target;
return HResult.E_FAIL;
}
Debug.Assert(Instance == null);
- Instance = new HostServices();
+ Instance = new HostServices(extensionPath, extensionLibrary);
return initialializeCallback(Instance.IHostServices);
}
- private HostServices()
+ private HostServices(string extensionPath, IntPtr extensionsLibrary)
{
+ SOSPath = Path.GetDirectoryName(extensionPath);
+ SOSHandle = extensionsLibrary;
+
_serviceManager = new ServiceManager();
- _commandService = new CommandService(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!ext" : null);
+ _commandService = new CommandService(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ">!sos" : null);
_serviceManager.NotifyExtensionLoad.Register(_commandService.AddCommands);
_symbolService = new SymbolService(this)
builder.AddMethod(new FlushTargetDelegate(FlushTarget));
builder.AddMethod(new DestroyTargetDelegate(DestroyTarget));
builder.AddMethod(new DispatchCommandDelegate(DispatchCommand));
- builder.AddMethod(new DisplayHelpDelegate(DisplayHelp));
builder.AddMethod(new UninitializeDelegate(Uninitialize));
IHostServices = builder.Complete();
FileLoggingConsoleService fileLoggingConsoleService = new(consoleService);
DiagnosticLoggingService.Instance.SetConsole(consoleService, fileLoggingConsoleService);
- // Don't register everything in the SOSHost assembly; just the wrappers
- _serviceManager.RegisterExportedServices(typeof(TargetWrapper));
- _serviceManager.RegisterExportedServices(typeof(RuntimeWrapper));
-
// Register all the services and commands in the Microsoft.Diagnostics.DebugServices.Implementation assembly
_serviceManager.RegisterAssembly(typeof(Target).Assembly);
// Register all the services and commands in the SOS.Extensions (this) assembly
_serviceManager.RegisterAssembly(typeof(HostServices).Assembly);
+ // Register all the services and commands in the SOS.Hosting assembly
+ _serviceManager.RegisterAssembly(typeof(SOSHost).Assembly);
+
// Register all the services and commands in the Microsoft.Diagnostics.ExtensionCommands assembly
_serviceManager.RegisterAssembly(typeof(ClrMDHelper).Assembly);
_serviceContainer = _serviceManager.CreateServiceContainer(ServiceScope.Global, parent: null);
_serviceContainer.AddService<IServiceManager>(_serviceManager);
_serviceContainer.AddService<IHost>(this);
+ _serviceContainer.AddService<SOSLibrary.ISOSModule>(this);
+ _serviceContainer.AddService<SOSHost.INativeClient>(DebuggerServices);
_serviceContainer.AddService<ICommandService>(_commandService);
_serviceContainer.AddService<ISymbolService>(_symbolService);
_serviceContainer.AddService<IConsoleService>(fileLoggingConsoleService);
ThreadUnwindServiceFromDebuggerServices threadUnwindService = new(DebuggerServices);
_serviceContainer.AddService<IThreadUnwindService>(threadUnwindService);
+ // Used to invoke only managed commands
+ _servicesWithManagedOnlyFilter = new(_contextService.Services);
+ _servicesWithManagedOnlyFilter.AddService(new SOSCommandBase.ManagedOnlyCommandFilter());
+
// Add each extension command to the native debugger
foreach ((string name, string help, IEnumerable<string> aliases) in _commandService.Commands)
{
Trace.TraceWarning($"Cannot add extension command {hr:X8} {name} - {help}");
}
}
-
- if (DebuggerServices.DebugClient is IDebugControl5 control)
- {
- MemoryRegionServiceFromDebuggerServices memRegions = new(DebuggerServices.DebugClient, control);
- _serviceContainer.AddService<IMemoryRegionService>(memRegions);
- }
}
catch (Exception ex)
{
{
return HResult.E_INVALIDARG;
}
- if (!_commandService.IsCommand(commandName))
- {
- return HResult.E_NOTIMPL;
- }
try
{
- StringBuilder sb = new();
- sb.Append(commandName);
- if (!string.IsNullOrWhiteSpace(commandArguments))
- {
- sb.Append(' ');
- sb.Append(commandArguments);
- }
- if (_commandService.Execute(sb.ToString(), _contextService.Services))
+ if (_commandService.Execute(commandName, commandArguments, commandName == "help" ? _contextService.Services : _servicesWithManagedOnlyFilter))
{
return HResult.S_OK;
}
- }
- catch (CommandNotSupportedException)
- {
- return HResult.E_NOTIMPL;
- }
- catch (Exception ex)
- {
- Trace.TraceError(ex.ToString());
- }
- return HResult.E_FAIL;
- }
-
- private int DisplayHelp(
- IntPtr self,
- string commandName)
- {
- try
- {
- if (!_commandService.DisplayHelp(commandName, _contextService.Services))
+ else
{
- return HResult.E_INVALIDARG;
+ // The command was not found or supported
+ return HResult.E_NOTIMPL;
}
}
- catch (CommandNotSupportedException)
- {
- return HResult.E_NOTIMPL;
- }
catch (Exception ex)
{
Trace.TraceError(ex.ToString());
- return HResult.E_FAIL;
+ IConsoleService consoleService = Services.GetService<IConsoleService>();
+ // TODO: when we can figure out how to deal with error messages in the scripts that are displayed on STDERROR under lldb
+ //consoleService.WriteLineError(ex.Message);
+ consoleService.WriteLine(ex.Message);
}
- return HResult.S_OK;
+ return HResult.E_FAIL;
}
private void Uninitialize(
#endregion
+ #region SOSLibrary.ISOSModule
+
+ public string SOSPath { get; }
+
+ public IntPtr SOSHandle { get; }
+
+ #endregion
+
#region IHostServices delegates
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
[In, MarshalAs(UnmanagedType.LPStr)] string commandName,
[In, MarshalAs(UnmanagedType.LPStr)] string commandArguments);
- [UnmanagedFunctionPointer(CallingConvention.Winapi)]
- private delegate int DisplayHelpDelegate(
- [In] IntPtr self,
- [In, MarshalAs(UnmanagedType.LPStr)] string commandName);
-
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate void UninitializeDelegate(
[In] IntPtr self);
private readonly IDebugClient5 _client;
private readonly IDebugControl5 _control;
- public MemoryRegionServiceFromDebuggerServices(IDebugClient5 client, IDebugControl5 control)
+ public MemoryRegionServiceFromDebuggerServices(IDebugClient5 client)
{
_client = client;
- _control = control;
+ _control = (IDebugControl5)client;
}
public IEnumerable<IMemoryRegion> EnumerateRegions()
_serviceContainerFactory.AddServiceFactory<ICrashInfoService>((services) => CreateCrashInfoService(services, debuggerServices));
OnFlushEvent.Register(() => FlushService<ICrashInfoService>());
+ if (debuggerServices.DebugClient is not null)
+ {
+ _serviceContainerFactory.AddServiceFactory<IMemoryRegionService>((services) => new MemoryRegionServiceFromDebuggerServices(debuggerServices.DebugClient));
+ }
+
Finished();
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Diagnostics;
-using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using Microsoft.Diagnostics.DebugServices;
namespace SOS.Hosting
[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 = "soshelp", DefaultOptions = "Help", Help = "Displays help for a specific SOS command.")]
[Command(Name = "syncblk", DefaultOptions = "SyncBlk", Help = "Displays the SyncBlock holder info.")]
[Command(Name = "threadstate", DefaultOptions = "ThreadState", Help = "Pretty prints the meaning of a threads state.")]
- [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 = "watsonbuckets", DefaultOptions = "WatsonBuckets", Flags = CommandFlags.Windows, Help = "Displays the Watson buckets.")]
- public class SOSCommand : CommandBase
+ public class SOSCommand : SOSCommandBase
{
+ [FilterInvoke]
+ public static bool FilterInvoke(
+ [ServiceImport(Optional = true)] ManagedOnlyCommandFilter managedOnly,
+ [ServiceImport(Optional = true)] IRuntime runtime) =>
+ SOSCommandBase.Filter(managedOnly, runtime);
+ }
+
+ [Command(Name = "comstate", DefaultOptions = "COMState", Help = "Lists the COM apartment model for each thread.")]
+ [Command(Name = "dumprcw", DefaultOptions = "DumpRCW", Help = "Displays information about a Runtime Callable Wrapper.")]
+ [Command(Name = "dumpccw", DefaultOptions = "DumpCCW", Help = "Displays information about a COM Callable Wrapper.")]
+ [Command(Name = "dumppermissionset", DefaultOptions = "DumpPermissionSet", Help = "Displays a PermissionSet object (debug build only).")]
+ [Command(Name = "gchandleleaks", DefaultOptions = "GCHandleLeaks", Help = "Helps in tracking down GCHandle leaks.")]
+ [Command(Name = "watsonbuckets", DefaultOptions = "WatsonBuckets", Help = "Displays the Watson buckets.")]
+ public class WindowsSOSCommand : SOSCommandBase
+ {
+ /// <summary>
+ /// These commands are Windows only.
+ /// </summary>
+ [FilterInvoke]
+ public static bool FilterInvoke(
+ [ServiceImport(Optional = true)] ITarget target,
+ [ServiceImport(Optional = true)] ManagedOnlyCommandFilter managedOnly,
+ [ServiceImport(Optional = true)] IRuntime runtime) =>
+ target != null && target.OperatingSystem == OSPlatform.Windows && SOSCommandBase.Filter(managedOnly, runtime);
+ }
+
+ public class SOSCommandBase : CommandBase
+ {
+ /// <summary>
+ /// Empty service used to prevent native commands from being run
+ /// </summary>
+ public class ManagedOnlyCommandFilter
+ {
+ }
+
[Argument(Name = "arguments", Help = "Arguments to SOS command.")]
public string[] Arguments { get; set; }
public override void Invoke()
{
- try
- {
- Debug.Assert(Arguments != null && Arguments.Length > 0);
- string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim();
- SOSHost.ExecuteCommand(Arguments[0], arguments);
- }
- catch (Exception ex) when (ex is FileNotFoundException or EntryPointNotFoundException or InvalidOperationException)
- {
- WriteLineError(ex.Message);
- }
+ Debug.Assert(Arguments != null && Arguments.Length > 0);
+ string arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim();
+ SOSHost.ExecuteCommand(Arguments[0], arguments);
}
[HelpInvoke]
- public void InvokeHelp()
+ public string GetDetailedHelp()
{
- SOSHost.ExecuteCommand("Help", Arguments[0]);
+ return SOSHost.GetHelpText(Arguments[0]);
}
+
+ /// <summary>
+ /// Common native SOS command filter function.
+ /// </summary>
+ /// <param name="managedOnly">not null means to filter out the native C++ SOS commands</param>
+ /// <param name="runtime">runtime instance or null</param>
+ /// <returns></returns>
+ public static bool Filter(ManagedOnlyCommandFilter managedOnly, IRuntime runtime) =>
+ // This filters out these native C++ commands if requested by host (in this case SOS.Extensions) to prevent recursion.
+ managedOnly == null &&
+ // This commands require a .NET Core, Desktop Framework or .NET Core single file runtime (not a Native AOT runtime)
+ runtime != null &&
+ (runtime.RuntimeType == RuntimeType.NetCore ||
+ runtime.RuntimeType == RuntimeType.Desktop ||
+ runtime.RuntimeType == RuntimeType.SingleFile);
}
}
[ServiceExport(Scope = ServiceScope.Target)]
public sealed class SOSHost : IDisposable
{
+ /// <summary>
+ /// Provides the native debugger's debug client instance
+ /// </summary>
+ public interface INativeClient
+ {
+ /// <summary>
+ /// Native debugger client interface
+ /// </summary>
+ IntPtr Client { get; }
+ }
+
// This is what dbgeng/IDebuggerServices returns for non-PE modules that don't have a timestamp
internal const uint InvalidTimeStamp = 0xFFFFFFFE;
internal const uint InvalidChecksum = 0xFFFFFFFF;
private readonly SOSLibrary _sosLibrary;
#pragma warning restore
- private readonly IntPtr _interface;
+ private readonly IntPtr _client;
private readonly ulong _ignoreAddressBitsMask;
- private bool _disposed;
+ private readonly bool _releaseClient;
/// <summary>
/// Create an instance of the hosting class. Has the lifetime of the target.
/// </summary>
- public SOSHost(ITarget target, IMemoryService memoryService)
+ public SOSHost(ITarget target, IMemoryService memoryService, [ServiceImport(Optional = true)] INativeClient client)
{
- Target = target ?? throw new DiagnosticsException("No target");
+ Target = target;
MemoryService = memoryService;
_ignoreAddressBitsMask = memoryService.SignExtensionMask();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ // If running under a native debugger, use the client instance supplied by the debugger for commands
+ if (client != null)
{
- DebugClient debugClient = new(this);
- _interface = debugClient.IDebugClient;
+ _client = client.Client;
}
else
{
- LLDBServices lldbServices = new(this);
- _interface = lldbServices.ILLDBServices;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ DebugClient debugClient = new(this);
+ _client = debugClient.IDebugClient;
+ }
+ else
+ {
+ LLDBServices lldbServices = new(this);
+ _client = lldbServices.ILLDBServices;
+ }
+ _releaseClient = true;
}
}
void IDisposable.Dispose()
{
- Trace.TraceInformation($"SOSHost.Dispose {_disposed}");
- if (!_disposed)
+ Trace.TraceInformation($"SOSHost.Dispose");
+ if (_releaseClient)
{
- _disposed = true;
- ComWrapper.ReleaseWithCheck(_interface);
+ ComWrapper.ReleaseWithCheck(_client);
}
}
/// <summary>
/// Execute a SOS command.
/// </summary>
- /// <param name="commandLine">command name and arguments</param>
- public void ExecuteCommand(string commandLine)
- {
- string command = "Help";
- string arguments = null;
-
- if (commandLine != null)
- {
- int firstSpace = commandLine.IndexOf(' ');
- command = firstSpace == -1 ? commandLine : commandLine.Substring(0, firstSpace);
- arguments = firstSpace == -1 ? null : commandLine.Substring(firstSpace);
- }
- ExecuteCommand(command, arguments);
- }
+ /// <param name="command">just the command name</param>
+ /// <param name="arguments">the command arguments and options</param>
+ public void ExecuteCommand(string command, string arguments) => _sosLibrary.ExecuteCommand(_client, command, arguments);
/// <summary>
- /// Execute a SOS command.
+ /// Get the detailed help text for a native SOS command.
/// </summary>
- /// <param name="command">just the command name</param>
- /// <param name="arguments">the command arguments and options</param>
- public void ExecuteCommand(string command, string arguments)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("SOSHost instance disposed");
- }
- _sosLibrary.ExecuteCommand(_interface, command, arguments);
- }
+ /// <param name="command">command name</param>
+ /// <returns>help text or null if not found or error</returns>
+ public string GetHelpText(string command) => _sosLibrary.GetHelpText(command);
#region Reverse PInvoke Implementations
/// </summary>
public sealed class SOSLibrary : IDisposable
{
+ /// <summary>
+ /// Provides the SOS module handle
+ /// </summary>
+ public interface ISOSModule
+ {
+ /// <summary>
+ /// The SOS module path
+ /// </summary>
+ string SOSPath { get; }
+
+ /// <summary>
+ /// The SOS module handle
+ /// </summary>
+ IntPtr SOSHandle { get; }
+ }
+
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int SOSCommandDelegate(
IntPtr ILLDBServices,
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate void SOSUninitializeDelegate();
+ [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, EntryPoint = "FindResourceA")]
+ public static extern IntPtr FindResource(IntPtr hModule, string name, string type);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResource);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr LockResource(IntPtr hResource);
+
private const string SOSInitialize = "SOSInitializeByHost";
private const string SOSUninitialize = "SOSUninitializeByHost";
private readonly HostWrapper _hostWrapper;
+ private readonly bool _uninitializeLibrary;
private IntPtr _sosLibrary = IntPtr.Zero;
/// <summary>
public string SOSPath { get; set; }
[ServiceExport(Scope = ServiceScope.Global)]
- public static SOSLibrary Create(IHost host)
+ public static SOSLibrary TryCreate(IHost host, [ServiceImport(Optional = true)] ISOSModule sosModule)
{
SOSLibrary sosLibrary = null;
try
{
- sosLibrary = new SOSLibrary(host);
+ sosLibrary = new SOSLibrary(host, sosModule);
sosLibrary.Initialize();
}
catch
/// Create an instance of the hosting class
/// </summary>
/// <param name="target">target instance</param>
- private SOSLibrary(IHost host)
+ /// <param name="host">sos library info or null</param>
+ private SOSLibrary(IHost host, ISOSModule sosModule)
{
- string rid = InstallHelper.GetRid();
- SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
+ if (sosModule is not null)
+ {
+ SOSPath = sosModule.SOSPath;
+ _sosLibrary = sosModule.SOSHandle;
+ }
+ else
+ {
+ string rid = InstallHelper.GetRid();
+ SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
+ _uninitializeLibrary = true;
+ }
_hostWrapper = new HostWrapper(host);
}
/// </summary>
private void Uninitialize()
{
- Trace.TraceInformation("SOSHost: Uninitialize");
- if (_sosLibrary != IntPtr.Zero)
+ Trace.TraceInformation("SOSLibrary: Uninitialize");
+ if (_uninitializeLibrary && _sosLibrary != IntPtr.Zero)
{
SOSUninitializeDelegate uninitializeFunc = SOSHost.GetDelegateFunction<SOSUninitializeDelegate>(_sosLibrary, SOSUninitialize);
uninitializeFunc?.Invoke();
Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.FreeLibrary(_sosLibrary);
- _sosLibrary = IntPtr.Zero;
}
+ _sosLibrary = IntPtr.Zero;
_hostWrapper.ReleaseWithCheck();
}
SOSCommandDelegate commandFunc = SOSHost.GetDelegateFunction<SOSCommandDelegate>(_sosLibrary, command);
if (commandFunc == null)
{
- throw new DiagnosticsException($"SOS command not found: {command}");
+ throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{command}'");
}
int result = commandFunc(client, arguments ?? "");
if (result == HResult.E_NOTIMPL)
{
- throw new CommandNotSupportedException($"SOS command not found: {command}");
+ throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{command}'");
}
if (result != HResult.S_OK)
{
Trace.TraceError($"SOS command FAILED 0x{result:X8}");
+ throw new DiagnosticsException(string.Empty);
+ }
+ }
+
+ public string GetHelpText(string command)
+ {
+ string helpText;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IntPtr hResInfo = FindResource(_sosLibrary, "DOCUMENTATION", "TEXT");
+ if (hResInfo == IntPtr.Zero)
+ {
+ throw new DiagnosticsException("Can not SOS help text");
+ }
+ IntPtr hResource = LoadResource(_sosLibrary, hResInfo);
+ if (hResource == IntPtr.Zero)
+ {
+ throw new DiagnosticsException("Can not SOS help text");
+ }
+ IntPtr helpTextPtr = LockResource(hResource);
+ if (helpTextPtr == IntPtr.Zero)
+ {
+ throw new DiagnosticsException("Can not SOS help text");
+ }
+ helpText = Marshal.PtrToStringAnsi(helpTextPtr);
+ }
+ else
+ {
+ string helpTextFile = Path.Combine(SOSPath, "sosdocsunix.txt");
+ helpText = File.ReadAllText(helpTextFile);
+ }
+ command = command.ToLowerInvariant();
+ string searchString = $"COMMAND: {command}.";
+
+ // Search for command in help text file
+ int start = helpText.IndexOf(searchString);
+ if (start == -1)
+ {
+ throw new DiagnosticsException($"Documentation for {command} not found");
+ }
+
+ // Go to end of line
+ start = helpText.IndexOf('\n', start);
+ if (start == -1)
+ {
+ throw new DiagnosticsException($"No newline in documentation resource or file");
+ }
+
+ // Find the first occurrence of \\ followed by an \r or an \n on a line by itself.
+ int end = start++;
+ while (true)
+ {
+ end = helpText.IndexOf("\\\\", end + 1);
+ if (end == -1)
+ {
+ break;
+ }
+ char c = helpText[end - 1];
+ if (c is '\r' or '\n')
+ {
+ break;
+ }
+ c = helpText[end + 3];
+ if (c is '\r' or '\n')
+ {
+ break;
+ }
}
+ return end == -1 ? helpText.Substring(start) : helpText.Substring(start, end - start);
}
}
}
<BuildProjectFramework>net6.0</BuildProjectFramework>
<RuntimeFrameworkVersion>$(RuntimeVersion60)</RuntimeFrameworkVersion>
</Option>
+ <!--
+ SOS.TestExtensions
+ -->
+ <Option Condition="'$(RuntimeVersion60)' != ''">
+ <TestName>SOS.TestExtensions</TestName>
+ <BuildProjectFramework>net6.0</BuildProjectFramework>
+ <RuntimeFrameworkVersion>$(RuntimeVersion60)</RuntimeFrameworkVersion>
+ <DotNetDiagnosticExtensions>$(RootBinDir)/bin/TestExtension/$(TargetConfiguration)/netstandard2.0/TestExtension.dll</DotNetDiagnosticExtensions>
+ <SetHostRuntime>$(DotNetRoot)/shared/Microsoft.NETCore.App/$(RuntimeFrameworkVersion)</SetHostRuntime>
+ </Option>
<!--
SOS.StackAndOtherTests (cli because tested with embedded, portable PDBs and single-file)
-->
<BuildProjectFramework>net6.0</BuildProjectFramework>
<RuntimeFrameworkVersion>$(RuntimeVersion60)</RuntimeFrameworkVersion>
</Option>
+ <!--
+ SOS.TestExtensions
+ -->
+ <Option Condition="'$(RuntimeVersion60)' != ''">
+ <TestName>SOS.TestExtensions</TestName>
+ <BuildProjectFramework>net6.0</BuildProjectFramework>
+ <RuntimeFrameworkVersion>$(RuntimeVersion60)</RuntimeFrameworkVersion>
+ <DotNetDiagnosticExtensions>$(RootBinDir)\bin\TestExtension\$(TargetConfiguration)\netstandard2.0\TestExtension.dll</DotNetDiagnosticExtensions>
+ <Options>
+ <Option>
+ <SetHostRuntime>$(DotNetRoot)/shared/Microsoft.NETCore.App/$(RuntimeFrameworkVersion)</SetHostRuntime>
+ </Option>
+ <Option>
+ <SetHostRuntime>-netfx</SetHostRuntime>
+ </Option>
+ </Options>
+ </Option>
<!--
SOS.StackAndOtherTests (cli because tested with full, embedded and portable PDBs)
-->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.TestHelpers\Microsoft.Diagnostics.TestHelpers.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\tests\TestExtension\TestExtension.csproj" />
</ItemGroup>
<ItemGroup>
{
throw new SkipTestException("This test validates POH behavior, which was introduced in .net 5");
}
-
await SOSTestHelpers.RunTest(
config,
debuggeeName: "GCPOH",
testName: "SOS.StackTests");
}
+ [SkippableTheory, MemberData(nameof(SOSTestHelpers.GetConfigurations), "TestName", "SOS.TestExtensions", MemberType = typeof(SOSTestHelpers))]
+ public async Task TestExtensions(TestConfiguration config)
+ {
+ await SOSTestHelpers.RunTest(
+ config,
+ debuggeeName: "LineNums",
+ scriptName: "TestExtensions.script",
+ Output,
+ testName: "SOS.TestExtensions");
+ }
+
[SkippableTheory, MemberData(nameof(Configurations))]
public async Task OtherCommands(TestConfiguration config)
{
using System.IO;
using System.IO.Pipes;
using System.Linq;
+using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
// Issue: https://github.com/dotnet/diagnostics/issues/3126
processRunner.WithRuntimeConfiguration("EnableWriteXorExecute", "0");
+ // Setup the extension environment variable
+ string extensions = config.DotNetDiagnosticExtensions();
+ if (!string.IsNullOrEmpty(extensions))
+ {
+ processRunner.WithEnvironmentVariable("DOTNET_DIAGNOSTIC_EXTENSIONS", extensions);
+ }
+
DumpType? dumpType = null;
if (action is DebuggerAction.LoadDump or DebuggerAction.LoadDumpWithDotNetDump)
{
{
await ContinueExecution();
}
+ // Adds the "!" prefix under dbgeng, nothing under lldb. Meant for SOS (native) commands.
else if (line.StartsWith("SOSCOMMAND:"))
{
string input = line.Substring("SOSCOMMAND:".Length).TrimStart();
throw new Exception($"SOS command FAILED: {input}");
}
}
+ else if (line.StartsWith("SOSCOMMAND_FAIL:"))
+ {
+ string input = line.Substring("SOSCOMMAND_FAIL:".Length).TrimStart();
+ if (await RunSosCommand(input))
+ {
+ // The cdb runcommand extension doesn't get the execute command failures (limitation in dbgeng).
+ if (Debugger != NativeDebugger.Cdb)
+ {
+ throw new Exception($"SOS command did not fail: {input}");
+ }
+ }
+ }
+ // Adds the "!sos" prefix under dbgeng, "sos " under lldb. Meant for extensions (managed) commands
else if (line.StartsWith("EXTCOMMAND:"))
{
string input = line.Substring("EXTCOMMAND:".Length).TrimStart();
throw new Exception($"Extension command FAILED: {input}");
}
}
+ else if (line.StartsWith("EXTCOMMAND_FAIL:"))
+ {
+ string input = line.Substring("EXTCOMMAND_FAIL:".Length).TrimStart();
+ if (await RunSosCommand(input, extensionCommand: true))
+ {
+ // The cdb runcommand extension doesn't get the execute command failures (limitation in dbgeng).
+ if (Debugger != NativeDebugger.Cdb)
+ {
+ throw new Exception($"Extension command did not fail: {input}");
+ }
+ }
+ }
+ // Never adds any prefix. Meant for native debugger commands.
else if (line.StartsWith("COMMAND:"))
{
string input = line.Substring("COMMAND:".Length).TrimStart();
throw new Exception($"Debugger command FAILED: {input}");
}
}
+ else if (line.StartsWith("COMMAND_FAIL:"))
+ {
+ string input = line.Substring("COMMAND_FAIL:".Length).TrimStart();
+ if (await RunCommand(input))
+ {
+ // The cdb runcommand extension doesn't get the execute command failures (limitation in dbgeng).
+ if (Debugger != NativeDebugger.Cdb)
+ {
+ throw new Exception($"Debugger command did not fail: {input}");
+ }
+ }
+ }
else if (line.StartsWith("VERIFY:"))
{
string verifyLine = line.Substring("VERIFY:".Length);
}
break;
case NativeDebugger.Lldb:
- command = "sos " + command;
- break;
case NativeDebugger.DotNetDump:
if (extensionCommand)
{
return TestConfiguration.MakeCanonicalPath(dotnetDumpPath);
}
+ public static string DotNetDiagnosticExtensions(this TestConfiguration config)
+ {
+ return TestConfiguration.MakeCanonicalPath(config.GetValue("DotNetDiagnosticExtensions"));
+ }
+
public static string SOSPath(this TestConfiguration config)
{
return TestConfiguration.MakeCanonicalPath(config.GetValue("SOSPath"));
# Load SOS even though it doesn't actually load the sos module on dotnet-dump but it runs some initial settings/commands.
LOADSOS
-EXTCOMMAND: dcd
+EXTCOMMAND_FAIL: dcd
VERIFY: Missing ConcurrentDictionary address
-EXTCOMMAND: dcd abcdefgh
+EXTCOMMAND_FAIL: dcd abcdefgh
VERIFY: Hexadecimal address expected
-EXTCOMMAND: dcd 0000000000000001
+EXTCOMMAND_FAIL: dcd 0000000000000001
VERIFY: is not referencing an object
# Checks on ConcurrentDictionary<int, string[]>
VERIFY:\[.*[\\|/]Debuggees[\\|/].*DivZero[\\|/]DivZero\.cs @ (57|56)\s*\]\s*
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
# Load SOS even though it doesn't actually load the sos module on dotnet-dump but it runs some initial settings/commands.
LOADSOS
-EXTCOMMAND: dumpgen
+EXTCOMMAND_FAIL: dumpgen
VERIFY: Generation argument is missing
-EXTCOMMAND: dumpgen invalid
+EXTCOMMAND_FAIL: dumpgen invalid
VERIFY: invalid is not a supported generation
!IFDEF:LLDB
-EXTCOMMAND: dumpgen gen0 -mt
+EXTCOMMAND_FAIL: dumpgen gen0 -mt
VERIFY: Required argument missing for option: -mt
-EXTCOMMAND: dumpgen gen1 -mt zzzzz
+EXTCOMMAND_FAIL: dumpgen gen1 -mt zzzzz
VERIFY: Hexadecimal address expected for -mt option
ENDIF:LLDB
IFDEF:WINDOWS
SOSCOMMAND:DumpHeap -stat -gen xxx
-VERIFY:\s*System\.ArgumentException: Unknown generation: xxx\. Only gen0, gen1, gen2, loh \(large\), poh \(pinned\) and foh \(frozen\) are supported\s+
+VERIFY:\s*Unknown generation: xxx\. Only gen0, gen1, gen2, loh \(large\), poh \(pinned\) and foh \(frozen\) are supported\s+
ENDIF:WINDOWS
IFDEF:WINDOWS
VERIFY:\[.*[\\|/]Debuggees[\\|/].*LineNums[\\|/]Program\.cs @ 13\s*\]\s*
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
VERIFY:There are nested exceptions on this thread. Run with -nested for details
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
LOADSOS
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads -managedexception
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads -managedexception
-ENDIF:DOTNETDUMP
# 5) Verifying that PrintException gives us the right exception in the format above.
SOSCOMMAND:PrintException
VERIFY:HResult:\s+80131604
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
VERIFY:\[.*[\\|/]Debuggees[\\|/].*SimpleThrow[\\|/]SimpleThrow\.cs @ 12\s*\]\s*
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
# Verify that "u" works (depends on the IP2MD here)
SOSCOMMAND:ClrStack
SOSCOMMAND:IP2MD <POUT>.*\s+(<HEXVAL>)\s+SymbolTestApp\.Program\.Foo4.*\s+<POUT>
-SOSCOMMAND:u <POUT>\s*MethodDesc:\s+(<HEXVAL>)\s*<POUT>
+SOSCOMMAND:clru <POUT>\s*MethodDesc:\s+(<HEXVAL>)\s*<POUT>
VERIFY:\s*Normal JIT generated code\s+
VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+
VERIFY:\s+Begin\s+<HEXVAL>,\s+size\s+<HEXVAL>\s+
VERIFY:\s+(?i:.*[\\|/]SymbolTestApp\.cs) @ (53|57):\s+
# Verify that "u" with no line info works
-SOSCOMMAND:u -n <PREVPOUT>
+SOSCOMMAND:clru -n <PREVPOUT>
VERIFY:\s*Normal JIT generated code\s+
VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+
VERIFY:\s+Begin\s+<HEXVAL>,\s+size\s+<HEXVAL>\s+
# Verify that "u" with offsets info works
-SOSCOMMAND:u -o <PREVPOUT>
+SOSCOMMAND:clru -o <PREVPOUT>
VERIFY:\s*Normal JIT generated code\s+
VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+
VERIFY:\s+Begin\s+<HEXVAL>,\s+size\s+<HEXVAL>\s+
VERIFY:\s+JITTED Code Address:\s+<HEXVAL>\s+
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
--- /dev/null
+CONTINUE
+
+LOADSOS
+
+SOSCOMMAND:clrstack
+VERIFY:Test command #1 invoked\s+
+
+SOSCOMMAND:dumpheap
+VERIFY:Test command #2 invoked\s+
+
+SOSCOMMAND:assemblies
+VERIFY:Test command #4 invoked\s+
+
+SOSCOMMAND_FAIL:ip2md 0
+!VERIFY:Test command #5 invoked\s+
VERIFY:.*\s+Stack walk complete.\s+
# Verify that Threads (clrthreads) works
-IFDEF:DOTNETDUMP
SOSCOMMAND:clrthreads
-ENDIF:DOTNETDUMP
-!IFDEF:DOTNETDUMP
-SOSCOMMAND:Threads
-ENDIF:DOTNETDUMP
VERIFY:\s*ThreadCount:\s+<DECVAL>\s+
VERIFY:\s+UnstartedThread:\s+<DECVAL>\s+
VERIFY:\s+BackgroundThread:\s+<DECVAL>\s+
VERIFY?:\s*<< Awaiting: <HEXVAL>\s+<HEXVAL>\s+.* >>\s+
VERIFY:\s*<HEXVAL>\s+<HEXVAL>\s+\((<DECVAL>)?\)\s+.*
-SOSCOMMAND:DumpMT --stats <PREVPOUT>
+SOSCOMMAND:DumpMT <PREVPOUT>
!VERIFY:\s*<HEXVAL> is not a MethodTable
SOSCOMMAND:DumpAsync --coalesce
gchist.cpp
gcroot.cpp
symbols.cpp
+ managedcommands.cpp
metadata.cpp
sigparser.cpp
sildasm.cpp
<ClCompile Include="exts.cpp" />
<ClCompile Include="gchist.cpp" />
<ClCompile Include="gcroot.cpp" />
+ <ClCompile Include="managedcommands.cpp" />
<ClCompile Include="metadata.cpp" />
<ClCompile Include="platform\datatarget.cpp" />
<ClCompile Include="platform\hostimpl.cpp" />
<ClCompile Include="platform\targetimpl.cpp">
<Filter>platform</Filter>
</ClCompile>
+ <ClCompile Include="managedcommands.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="data.h" />
<Text Include="sosdocs.txt" />
<Text Include="sosdocsunix.txt" />
</ItemGroup>
-</Project>
+</Project>
\ No newline at end of file
HRESULT
ExtQuery(ILLDBServices* services)
{
- // Initialize the PAL and extension suppport in one place and only once.
- if (!g_palInitialized)
+ if (!InitializePAL())
{
- if (PAL_InitializeDLL() != 0)
- {
- return E_FAIL;
- }
- g_palInitialized = true;
+ return E_FAIL;
}
g_ExtServices = services;
ReleaseTarget();
}
+// Executes managed extension commands. Returns E_NOTIMPL if the command doesn't exists.
+HRESULT
+ExecuteCommand(PCSTR commandName, PCSTR args)
+{
+ if (commandName != nullptr && strlen(commandName) > 0)
+ {
+ IHostServices* hostServices = GetHostServices();
+ if (hostServices != nullptr)
+ {
+ return hostServices->DispatchCommand(commandName, args);
+ }
+ }
+ return E_NOTIMPL;
+}
+
+void
+EENotLoadedMessage(HRESULT Status)
+{
+#ifdef FEATURE_PAL
+ ExtOut("Failed to find runtime module (%s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), Status);
+#else
+ ExtOut("Failed to find runtime module (%s or %s or %s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), GetRuntimeDllName(IRuntime::WindowsDesktop), GetRuntimeDllName(IRuntime::UnixCore), Status);
+#endif
+ ExtOut("Extension commands need it in order to have something to do.\n");
+ ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n");
+}
+
+void
+DACMessage(HRESULT Status)
+{
+ ExtOut("Failed to load data access module, 0x%08x\n", Status);
+ if (GetHost()->GetHostType() == IHost::HostType::DbgEng)
+ {
+ ExtOut("Verify that 1) you have a recent build of the debugger (10.0.18317.1001 or newer)\n");
+ ExtOut(" 2) the file %s that matches your version of %s is\n", GetDacDllName(), GetRuntimeDllName());
+ ExtOut(" in the version directory or on the symbol path\n");
+ ExtOut(" 3) or, if you are debugging a dump file, verify that the file\n");
+ ExtOut(" %s_<arch>_<arch>_<version>.dll is on your symbol path.\n", GetDacModuleName());
+ ExtOut(" 4) you are debugging on a platform and architecture that supports this\n");
+ ExtOut(" the dump file. For example, an ARM dump file must be debugged\n");
+ ExtOut(" on an X86 or an ARM machine; an AMD64 dump file must be\n");
+ ExtOut(" debugged on an AMD64 machine.\n");
+ ExtOut("\n");
+ ExtOut("You can run the command '!setclrpath <directory>' to control the load path of %s.\n", GetDacDllName());
+ ExtOut("\n");
+ ExtOut("Or you can also run the debugger command .cordll to control the debugger's\n");
+ ExtOut("load of %s. .cordll -ve -u -l will do a verbose reload.\n", GetDacDllName());
+ ExtOut("If that succeeds, the SOS command should work on retry.\n");
+ ExtOut("\n");
+ ExtOut("If you are debugging a minidump, you need to make sure that your executable\n");
+ ExtOut("path is pointing to %s as well.\n", GetRuntimeDllName());
+ }
+ else
+ {
+ if (Status == CORDBG_E_MISSING_DEBUGGER_EXPORTS)
+ {
+ ExtOut("You can run the debugger command 'setclrpath <directory>' to control the load of %s.\n", GetDacDllName());
+ ExtOut("If that succeeds, the SOS command should work on retry.\n");
+ }
+ else
+ {
+ ExtOut("Can not load or initialize %s. The target runtime may not be initialized.\n", GetDacDllName());
+ }
+ }
+ ExtOut("\n");
+ ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n");
+}
+
#ifndef FEATURE_PAL
BOOL IsMiniDumpFileNODAC();
#else // FEATURE_PAL
+BOOL
+InitializePAL()
+{
+ // Initialize the PAL only once
+ if (!g_palInitialized)
+ {
+ if (PAL_InitializeDLL() != 0)
+ {
+ return false;
+ }
+ g_palInitialized = true;
+ }
+ return true;
+}
+
HRESULT
DebugClient::QueryInterface(
REFIID InterfaceId,
extern ILLDBServices* g_ExtServices;
extern ILLDBServices2* g_ExtServices2;
+extern BOOL InitializePAL();
#define IsInitializedByDbgEng() false
void
ExtRelease(void);
+HRESULT
+ExecuteCommand(PCSTR commandName, PCSTR args);
+
+void
+EENotLoadedMessage(HRESULT Status);
+
+void
+DACMessage(HRESULT Status);
+
extern BOOL ControlC;
inline BOOL IsInterrupt()
~__ExtensionCleanUp(){ExtRelease();}
};
-inline void EENotLoadedMessage(HRESULT Status)
-{
-#ifdef FEATURE_PAL
- ExtOut("Failed to find runtime module (%s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), Status);
-#else
- ExtOut("Failed to find runtime module (%s or %s or %s), 0x%08x\n", GetRuntimeDllName(IRuntime::Core), GetRuntimeDllName(IRuntime::WindowsDesktop), GetRuntimeDllName(IRuntime::UnixCore), Status);
-#endif
- ExtOut("Extension commands need it in order to have something to do.\n");
- ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n");
-}
-
-inline void DACMessage(HRESULT Status)
-{
- ExtOut("Failed to load data access module, 0x%08x\n", Status);
- if (GetHost()->GetHostType() == IHost::HostType::DbgEng)
- {
- ExtOut("Verify that 1) you have a recent build of the debugger (10.0.18317.1001 or newer)\n");
- ExtOut(" 2) the file %s that matches your version of %s is\n", GetDacDllName(), GetRuntimeDllName());
- ExtOut(" in the version directory or on the symbol path\n");
- ExtOut(" 3) or, if you are debugging a dump file, verify that the file\n");
- ExtOut(" %s_<arch>_<arch>_<version>.dll is on your symbol path.\n", GetDacModuleName());
- ExtOut(" 4) you are debugging on a platform and architecture that supports this\n");
- ExtOut(" the dump file. For example, an ARM dump file must be debugged\n");
- ExtOut(" on an X86 or an ARM machine; an AMD64 dump file must be\n");
- ExtOut(" debugged on an AMD64 machine.\n");
- ExtOut("\n");
- ExtOut("You can run the command '!setclrpath <directory>' to control the load path of %s.\n", GetDacDllName());
- ExtOut("\n");
- ExtOut("Or you can also run the debugger command .cordll to control the debugger's\n");
- ExtOut("load of %s. .cordll -ve -u -l will do a verbose reload.\n", GetDacDllName());
- ExtOut("If that succeeds, the SOS command should work on retry.\n");
- ExtOut("\n");
- ExtOut("If you are debugging a minidump, you need to make sure that your executable\n");
- ExtOut("path is pointing to %s as well.\n", GetRuntimeDllName());
- }
- else
- {
- if (Status == CORDBG_E_MISSING_DEBUGGER_EXPORTS)
- {
- ExtOut("You can run the debugger command 'setclrpath <directory>' to control the load of %s.\n", GetDacDllName());
- ExtOut("If that succeeds, the SOS command should work on retry.\n");
- }
- else
- {
- ExtOut("Can not load or initialize %s. The target runtime may not be initialized.\n", GetDacDllName());
- }
- }
- ExtOut("\n");
- ExtOut("For more information see https://go.microsoft.com/fwlink/?linkid=2135652\n");
-}
-
// The minimum initialization for a command
#define INIT_API_EXT() \
HRESULT Status; \
INIT_API_EXT() \
if ((Status = ArchQuery()) != S_OK) return Status;
+#define INIT_API_NOEE_PROBE_MANAGED(name) \
+ INIT_API_EXT() \
+ if ((Status = ExecuteCommand(name, args)) != E_NOTIMPL) return Status; \
+ if ((Status = ArchQuery()) != S_OK) return Status;
+
#define INIT_API_EE() \
if ((Status = GetRuntime(&g_pRuntime)) != S_OK) \
{ \
INIT_API_NOEE() \
INIT_API_EE()
+#define INIT_API_NODAC_PROBE_MANAGED(name) \
+ INIT_API_NOEE_PROBE_MANAGED(name) \
+ INIT_API_EE()
+
#define INIT_API_DAC() \
if ((Status = LoadClrDebugDll()) != S_OK) \
{ \
ToRelease<ISOSDacInterface> spISD(g_sos); \
ResetGlobals();
+#define INIT_API_PROBE_MANAGED(name) \
+ INIT_API_NODAC_PROBE_MANAGED(name) \
+ INIT_API_DAC()
+
#define INIT_API() \
INIT_API_NODAC() \
INIT_API_DAC()
+#define INIT_API_EFN() \
+ INIT_API_NODAC() \
+ INIT_API_DAC()
+
// Attempt to initialize DAC and SOS globals, but do not "return" on failure.
// Instead, mark the failure to initialize the DAC by setting g_bDacBroken to TRUE.
// This should be used from extension commands that should work OK even when no
// runtime is loaded in the debuggee, e.g. DumpLog, DumpStack. These extensions
// and functions they call should test g_bDacBroken before calling any DAC enabled
// feature.
-#define INIT_API_NO_RET_ON_FAILURE() \
- INIT_API_NODAC() \
- if ((Status = LoadClrDebugDll()) != S_OK) \
+#define INIT_API_NO_RET_ON_FAILURE(name) \
+ INIT_API_NODAC_PROBE_MANAGED(name) \
+ if ((Status = LoadClrDebugDll()) != S_OK) \
{ \
ExtOut("Failed to load data access module (%s), 0x%08x\n", GetDacDllName(), Status); \
ExtOut("Some functionality may be impaired\n"); \
ToRelease<ISOSDacInterface> spISD(g_sos); \
ToRelease<IXCLRDataProcess> spIDP(g_clrData);
+#ifdef FEATURE_PAL
+
+#define MINIDUMP_NOT_SUPPORTED()
+#define ONLY_SUPPORTED_ON_WINDOWS_TARGET()
+
+#else // !FEATURE_PAL
+
+#define MINIDUMP_NOT_SUPPORTED() \
+ if (IsMiniDumpFile()) \
+ { \
+ ExtOut("This command is not supported in a minidump without full memory\n"); \
+ ExtOut("To try the command anyway, run !MinidumpMode 0\n"); \
+ return Status; \
+ }
+
+#define ONLY_SUPPORTED_ON_WINDOWS_TARGET() \
+ if (!IsWindowsTarget()) \
+ { \
+ ExtOut("This command is only supported for Windows targets\n"); \
+ return Status; \
+ }
+
+#endif // FEATURE_PAL
+
extern BOOL g_bDacBroken;
//-----------------------------------------------------------------------------------------
#include <stdint.h>
#include <string.h>
#include <stddef.h>
-
#include "strike.h"
+
// We need to define the target address type. This will be used in the
// functions that read directly from the debuggee address space, vs. using
// the DAC tgo read the DAC-ized data structures.
DECLARE_API(HistStats)
{
INIT_API();
-
+
ExtOut ("%8s %8s %8s\n",
"GCCount", "Promotes", "Relocs");
ExtOut ("-----------------------------------\n");
};
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
- return Status;
-
+ {
+ return E_INVALIDARG;
+ }
if (nArg != 1)
{
ExtOut ("!Root <valid object pointer>\n");
- return Status;
+ return E_INVALIDARG;
}
size_t Root = (size_t) GetExpression(rootstr.data);
};
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
- return Status;
-
+ {
+ return E_INVALIDARG;
+ }
if (nArg != 1)
{
ExtOut ("!ObjSearch <valid object pointer>\n");
- return Status;
+ return E_INVALIDARG;
}
size_t object = (size_t) GetExpression(objstr.data);
};
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
- return Status;
-
+ {
+ return E_INVALIDARG;
+ }
if (nArg != 1)
{
ExtOut ("!object <valid object pointer>\n");
- return Status;
+ return E_INVALIDARG;
}
size_t object = (size_t) GetExpression(objstr.data);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "exts.h"
+
+// Windows host only managed command stubs
+
+HRESULT ExecuteManagedOnlyCommand(PCSTR commandName, PCSTR args)
+{
+ HRESULT hr = ExecuteCommand(commandName, args);
+ if (hr == E_NOTIMPL)
+ {
+ ExtErr("Unrecognized command '%s'\n", commandName);
+ }
+ return hr;
+}
+
+DECLARE_API(DumpStackObjects)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("dumpstackobjects", args);
+}
+
+DECLARE_API(EEHeap)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("eeheap", args);
+}
+
+DECLARE_API(TraverseHeap)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("traverseheap", args);
+}
+
+DECLARE_API(DumpRuntimeTypes)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("dumpruntimetypes", args);
+}
+
+DECLARE_API(DumpHeap)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("dumpheap", args);
+}
+
+DECLARE_API(VerifyHeap)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("verifyheap", args);
+}
+
+DECLARE_API(AnalyzeOOM)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("analyzeoom", args);
+}
+
+DECLARE_API(VerifyObj)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("verifyobj", args);
+}
+
+DECLARE_API(ListNearObj)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("listnearobj", args);
+}
+
+DECLARE_API(GCHeapStat)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("gcheapstat", args);
+}
+
+DECLARE_API(FinalizeQueue)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("finalizequeue", args);
+}
+
+DECLARE_API(ThreadPool)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("threadpool", args);
+}
+
+DECLARE_API(PathTo)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("pathto", args);
+}
+
+DECLARE_API(GCRoot)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("gcroot", args);
+}
+
+DECLARE_API(GCWhere)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("gcwhere", args);
+}
+
+DECLARE_API(ObjSize)
+{
+ INIT_API_EXT();
+ MINIDUMP_NOT_SUPPORTED();
+ return ExecuteManagedOnlyCommand("objsize", args);
+}
+
+DECLARE_API(SetSymbolServer)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("setsymbolserver", args);
+}
+
+DECLARE_API(assemblies)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("assemblies", args);
+}
+
+DECLARE_API(crashinfo)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("crashinfo", args);
+}
+
+DECLARE_API(DumpAsync)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("dumpasync", args);
+}
+
+DECLARE_API(logging)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("logging", args);
+}
+
+DECLARE_API(maddress)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("maddress", args);
+}
+
+DECLARE_API(dumpexceptions)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("dumpexceptions", args);
+}
+
+DECLARE_API(dumpgen)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("dumpgen", args);
+}
+
+DECLARE_API(sizestats)
+{
+ INIT_API_EXT();
+ return ExecuteManagedOnlyCommand("sizestats", args);
+}
+
+typedef HRESULT (*PFN_COMMAND)(PDEBUG_CLIENT client, PCSTR args);
+
+//
+// Executes managed extension commands (i.e. !sos)
+//
+DECLARE_API(ext)
+{
+ INIT_API_EXT();
+
+ 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());
+ }
+ else
+ {
+ ExtErr("Unrecognized command '%s'\n", commandName.c_str());
+ }
+ }
+ return Status;
+}
+
AnalyzeOOM
analyzeoom=AnalyzeOOM
ao=AnalyzeOOM
- clrmodules
+ assemblies
+ clrmodules=assemblies
ClrStack
clrstack=ClrStack
CLRStack=ClrStack
dumpdelegate=DumpDelegate
DumpDomain
dumpdomain=DumpDomain
+ dumpexceptions
#ifdef TRACE_GC
DumpGCLog
dumpgclog=DumpGCLog
DumpGCConfigLog
dumpgcconfiglog=DumpGCConfigLog
dclog=DumpGCConfigLog
+ dumpgen
+ dg=dumpgen
DumpHeap
dumpheap=DumpHeap
DumpIL
ListNearObj
listnearobj=ListNearObj
lno=ListNearObj
+ maddress
Name2EE
name2ee=Name2EE
ObjSize
setsymbolserver=SetSymbolServer
SetClrPath
setclrpath=SetClrPath
+ sizestats
SOSFlush
sosflush=SOSFlush
StopOnException
Traverseheap=TraverseHeap
u
U=u
+ clru=u
VerifyHeap
verifyheap=VerifyHeap
Verifyheap=VerifyHeap
; The .NET Foundation licenses this file to you under the MIT license.
; See the LICENSE file in the project root for more information.
-AnalyzeOOM
bpmd
ClrStack
dbgout
DumpDelegate
DumpDomain
DumpGCData
-DumpHeap
DumpIL
DumpLog
DumpMD
DumpModule
DumpMT
DumpObj
-DumpRuntimeTypes
DumpSig
DumpSigElem
DumpStack
-DumpStackObjects
DumpVC
-EEHeap
EEVersion
EEStack
EHInfo
enummem
-FinalizeQueue
FindAppDomain
FindRoots
GCHandles
-GCHeapStat
GCInfo
-GCRoot
-GCWhere
Help
HistClear
HistInit
HistRoot
HistStats
IP2MD
-ListNearObj
Name2EE
-ObjSize
PrintException
-PathTo
runtimes
StopOnCatch
SetClrPath
SuppressJitOptimization
SyncBlk
Threads
-ThreadPool
ThreadState
Token2EE
-TraverseHeap
u
-VerifyHeap
-VerifyObj
SOSInitializeByHost
SOSUninitializeByHost
SOS is a debugger extension DLL designed to aid in the debugging of managed
programs. Functions are listed by category, then roughly in order of
importance. Shortcut names for popular functions are listed in parenthesis.
-Type "!help <functionname>" for detailed info on that function.
+Type "!soshelp <functionname>" for detailed info on that function.
Object Inspection Examining code and stacks
----------------------------- -----------------------------
.NET Core runtime.
\\
-COMMAND: setsymbolserver.
-!SetSymbolServer [-ms] [-mi] [-disable] [-log] [-cache <cache-path>] [-directory <search-directory>] [-timeout <minutes> ] [-pat <token>] [<symbol-server-URL>]
-
--ms - Use the public Microsoft symbol server.
--mi - Use the internal symweb symbol server.
--disable - Disable symbol download support.
--directory - Directory to search for symbols. Can be more than one.
--timeout - Specify the symbol server timeout in minutes
--pat - Access token to the authenticated server.
--cache - Specific a symbol cache directory. The default is %%TEMP%%\SymbolCache if not specified.
-<symbol-server-URL> - Symbol server URL.
-
-This commands enables symbol server support for portable PDBs in SOS. If the .sympath is set, this
-symbol server support is automatically enabled.
-
-To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set:
-
- 0:000> !setsymbolserver -disable
-\\
-
COMMAND: sosstatus.
!SOSStatus [-reset]
List and select the .NET runtimes in the target process.
\\
-COMMAND: logging.
-logging [enable] [disable]
-
-Enables or disables the internal trace logging.
-\\
.NET Core runtime.
\\
-COMMAND: setsymbolserver.
-COMMAND: loadsymbols.
-SetSymbolServer [-ms] [-disable] [-log] [-loadsymbols] [-cache <cache-path>] [-directory <search-directory>] [-timeout <minutes> ] [-pat <token>] [<symbol-server-URL>]
-
--ms - Use the public Microsoft symbol server.
--disable - Disable symbol download support.
--directory - Directory to search for symbols. Can be more than one.
--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.
--loadsymbols - Attempts to download the native .NET Core symbols for the runtime
-<symbol-server-URL> - Symbol server URL.
-
-This commands enables symbol server support in SOS. The portable PDBs for managed assemblies
-and .NET Core native symbol files are downloaded.
-
-To enable downloading symbols from the Microsoft symbol server:
-
- (lldb) setsymbolserver -ms
-
-This command may take some time without any output while it attempts to download the symbol files.
-
-To disable downloading or clear the current SOS symbol settings allowing new symbol paths to be set:
-
- (lldb) setsymbolserver -disable
-
-To add a directory to search for symbols:
-
- (lldb) setsymbolserver -directory /home/mikem/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.
-
-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
-runtime with the "sethostruntime" command. Type "soshelp sethostruntime" for more details.
-
- (lldb) setsymbolserver -ms
- Error: Fail to initialize CoreCLR 80004005
- SetSymbolServer -ms failed
-
-The "-loadsymbols" option and the "loadsymbol" command alias attempts to download the native .NET
-Core symbol files. It is only useful for live sessions and not core dumps. This command needs to
-be run before the lldb "bt" (stack trace) or the "clrstack -f" (dumps both managed and native
-stack frames).
-
- (lldb) loadsymbols
- (lldb) bt
-\\
-
COMMAND: sosstatus.
SOSStatus [-reset]
List the .NET runtimes in the target process.
\\
-COMMAND: logging.
-logging [enable] [disable]
-
-Enables or disables the internal trace logging.
-\\
#include <list>
#endif // !FEATURE_PAL
#include <wchar.h>
-
#include "platformspecific.h"
#define NOEXTAPI
#undef StackTrace
#include <dbghelp.h>
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
DECLARE_API (MinidumpMode)
{
- INIT_API ();
+ INIT_API();
ONLY_SUPPORTED_ON_WINDOWS_TARGET();
DWORD_PTR Value=0;
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg == 0)
{
\**********************************************************************/
DECLARE_API(IP2MD)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("ip2md");
MINIDUMP_NOT_SUPPORTED();
BOOL dml = FALSE;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
if (IP == 0)
{
ExtOut("%s is not IP\n", args);
- return Status;
+ return E_INVALIDARG;
}
CLRDATA_ADDRESS cdaStart = TO_CDADDR(IP);
return hr;
}
-
-//
-// Executes managed extension commands
-//
-HRESULT ExecuteCommand(PCSTR commandName, PCSTR args)
-{
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
- {
- if (commandName != nullptr && strlen(commandName) > 0)
- {
- return hostServices->DispatchCommand(commandName, args);
- }
- }
- ExtErr("Unrecognized command %s\n", commandName);
- return E_NOTIMPL;
-}
-
-
/**********************************************************************\
* Routine Description: *
* *
DECLARE_API(DumpStack)
{
- INIT_API_NO_RET_ON_FAILURE();
+ INIT_API_NO_RET_ON_FAILURE("dumpstack");
MINIDUMP_NOT_SUPPORTED();
};
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
- return Status;
+ {
+ return E_INVALIDARG;
+ }
// symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options
ULONG symlines = 0;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder enableDML(dml);
return Status;
}
-/**********************************************************************\
-* Routine Description: *
-* *
-* This function is called to dump the address and name of all *
-* Managed Objects on the stack. *
-* *
-\**********************************************************************/
-DECLARE_API(DumpStackObjects)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("dumpstackobjects", args);
-}
-
/**********************************************************************\
* Routine Description: *
* *
\**********************************************************************/
DECLARE_API(DumpMD)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpmd");
MINIDUMP_NOT_SUPPORTED();
DWORD_PTR dwStartAddr = NULL;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
\**********************************************************************/
DECLARE_API(DumpIL)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpil");
MINIDUMP_NOT_SUPPORTED();
DWORD_PTR dwStartAddr = NULL;
DWORD_PTR dwDynamicMethodObj = NULL;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg != 2)
{
ExtOut("%sdumpsig <sigaddr> <moduleaddr>\n", SOSPrefix);
- return Status;
+ return E_INVALIDARG;
}
DWORD_PTR dwSigAddr = GetExpression(sigExpr.data);
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg != 2)
{
ExtOut("%sdumpsigelem <sigaddr> <moduleaddr>\n", SOSPrefix);
- return Status;
+ return E_INVALIDARG;
}
DWORD_PTR dwSigAddr = GetExpression(sigExpr.data);
if (dwSigAddr == 0 || dwModuleAddr == 0)
{
ExtOut("Invalid parameters %s %s\n", sigExpr.data, moduleExpr.data);
- return Status;
+ return E_INVALIDARG;
}
DumpSigWorker(dwSigAddr, dwModuleAddr, FALSE);
\**********************************************************************/
DECLARE_API(DumpClass)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpclass");
MINIDUMP_NOT_SUPPORTED();
DWORD_PTR dwStartAddr = 0;
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg == 0)
{
ExtOut("Missing EEClass address\n");
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
DWORD_PTR dwStartAddr=0;
DWORD_PTR dwOriginalAddr;
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpmt");
MINIDUMP_NOT_SUPPORTED();
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
if (nArg == 0)
{
Print("Missing MethodTable address\n");
- return Status;
+ return E_INVALIDARG;
}
dwOriginalAddr = dwStartAddr;
if (!IsMethodTable(dwStartAddr))
{
Print(dwOriginalAddr, " is not a MethodTable\n");
- return Status;
+ return E_INVALIDARG;
}
DacpMethodTableData vMethTable;
if (vMethTable.bIsFree)
{
Print("Free MethodTable\n");
- return Status;
+ return E_INVALIDARG;
}
DacpMethodTableCollectibleData vMethTableCollectible;
\**********************************************************************/
DECLARE_API(DumpArray)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumparray");
DumpArrayFlags flags;
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
if (p_Object == 0)
{
ExtOut("Invalid parameter %s\n", flags.strObject);
- return Status;
+ return E_INVALIDARG;
}
if (!sos::IsObject(p_Object, true))
\**********************************************************************/
DECLARE_API(DumpObj)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpobj");
MINIDUMP_NOT_SUPPORTED();
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
DWORD_PTR p_Object = GetExpression(str_Object.data);
if (p_Object == 0)
{
ExtOut("Invalid parameter %s\n", args);
- return Status;
+ return E_INVALIDARG;
}
try {
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
DWORD_PTR p_Object = GetExpression(str_Object.data);
if (p_Object == 0)
{
ExtOut("Invalid parameter %s\n", args);
- return Status;
+ return E_INVALIDARG;
}
try
DECLARE_API(DumpDelegate)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpdelegate");
MINIDUMP_NOT_SUPPORTED();
try
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg != 1)
{
ExtOut("Usage: %sdumpdelegate <delegate object address>\n", SOSPrefix);
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
DECLARE_API(PrintException)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("printexception");
BOOL dml = FALSE;
BOOL bShowNested = FALSE;
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
CheckBreakingRuntimeChange();
{
ExtOut("Invalid exception object %s\n", args);
}
- return Status;
+ return E_INVALIDARG;
}
if (bCCW)
if ((threadAddr == NULL) || (Thread.Request(g_sos, threadAddr) != S_OK))
{
ExtOut("The current thread is unmanaged\n");
- return Status;
+ return E_INVALIDARG;
}
if (Thread.firstNestedException)
\**********************************************************************/
DECLARE_API(DumpVC)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("dumpvc");
MINIDUMP_NOT_SUPPORTED();
DWORD_PTR p_MT = NULL;
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg!=1)
{
#endif // _DEBUG
#endif // FEATURE_PAL
-/**********************************************************************\
-* Routine Description: *
-* *
-* This function dumps GC heap size. *
-* *
-\**********************************************************************/
-DECLARE_API(EEHeap)
-{
- INIT_API_EXT();
- return ExecuteCommand("eeheap", args);
-}
-
void PrintGCStat(HeapStat *inStat, const char* label=NULL)
{
if (inStat)
}
}
-DECLARE_API(TraverseHeap)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
- return ExecuteCommand("traverseheap", args);
-}
-
-DECLARE_API(DumpRuntimeTypes)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
- return ExecuteCommand("dumpruntimetypes", args);
-}
-
namespace sos
{
class FragmentationBlock
};
}
-DECLARE_API(DumpHeap)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
- return ExecuteCommand("dumpheap", args);
-}
-
-DECLARE_API(VerifyHeap)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
- return ExecuteCommand("verifyheap", args);
-}
-
-DECLARE_API(AnalyzeOOM)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("analyzeoom", args);
-}
-
-DECLARE_API(VerifyObj)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("verifyobj", args);
-}
-
-DECLARE_API(ListNearObj)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("listnearobj", args);
-}
-
-DECLARE_API(GCHeapStat)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("gcheapstat", args);
-}
-
/**********************************************************************\
* Routine Description: *
* *
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
}
#endif // FEATURE_COMINTEROP
-/**********************************************************************\
-* Routine Description: *
-* *
-* This function is called to dump the contents of the finalizer *
-* queue. *
-* *
-\**********************************************************************/
-DECLARE_API(FinalizeQueue)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("finalizequeue", args);
-}
-
enum {
// These are the values set in m_dwTransientFlags.
// Note that none of these flags survive a prejit save/restore.
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg != 1)
{
ExtOut("Usage: DumpModule [-mt] <Module Address>\n");
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
DECLARE_API(Threads)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("clrthreads");
BOOL bPrintSpecialThreads = FALSE;
BOOL bPrintLiveThreadsOnly = FALSE;
};
if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
{
- return Status;
+ return E_INVALIDARG;
}
if (bSwitchToManagedExceptionThread)
HRESULT HandleRuntimeLoadedNotification(IDebugClient* client)
{
- INIT_API();
+ INIT_API_EFN();
EnableModuleLoadUnloadCallbacks();
return g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, "sxe -c \"!SOSHandleCLRN\" clrn", 0);
}
HRESULT HandleExceptionNotification(ILLDBServices *client)
{
- INIT_API();
+ INIT_API_EFN();
return HandleCLRNotificationEvent();
}
HRESULT HandleRuntimeLoadedNotification(ILLDBServices *client)
{
- INIT_API();
+ INIT_API_EFN();
EnableModuleLoadUnloadCallbacks();
return g_ExtServices->SetExceptionCallback(HandleExceptionNotification);
}
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
bool fBadParam = false;
return Status;
}
-/**********************************************************************\
-* Routine Description: *
-* *
-* This function is called to dump the managed threadpool *
-* *
-\**********************************************************************/
-DECLARE_API(ThreadPool)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("threadpool", args);
-}
-
DECLARE_API(FindAppDomain)
{
INIT_API();
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
DECLARE_API(EHInfo)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("ehinfo");
MINIDUMP_NOT_SUPPORTED();
DWORD_PTR dwStartAddr = NULL;
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || (0 == nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
\**********************************************************************/
DECLARE_API(GCInfo)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("gcinfo");
MINIDUMP_NOT_SUPPORTED();
TADDR taStartAddr = NULL;
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || (0 == nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
};
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || (nArg < 1))
{
- return Status;
+ return E_INVALIDARG;
}
// symlines will be non-zero only if SYMOPT_LOAD_LINES was set in the symbol options
ULONG symlines = 0;
\**********************************************************************/
DECLARE_API(DumpLog)
{
- INIT_API_NO_RET_ON_FAILURE();
-
+ INIT_API_NO_RET_ON_FAILURE("dumplog");
MINIDUMP_NOT_SUPPORTED();
-
_ASSERTE(g_pRuntime != nullptr);
// Not supported on desktop runtime
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg > 0 && sFileName.data != NULL)
{
\**********************************************************************/
DECLARE_API(EEVersion)
{
- INIT_API_NO_RET_ON_FAILURE();
+ INIT_API_NO_RET_ON_FAILURE("eeversion");
static const int fileVersionBufferSize = 1024;
ArrayHolder<char> fileVersionBuffer = new char[fileVersionBufferSize];
\**********************************************************************/
DECLARE_API(SOSStatus)
{
- INIT_API_NOEE();
+ INIT_API_NOEE_PROBE_MANAGED("sosstatus");
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
+ BOOL bReset = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-reset", &bReset, COBOOL, FALSE},
+ {"--reset", &bReset, COBOOL, FALSE},
+ {"-r", &bReset, COBOOL, FALSE},
+ };
+ if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
{
- Status = hostServices->DispatchCommand("sosstatus", args);
+ return E_INVALIDARG;
}
- else
+ if (bReset)
{
- BOOL bReset = FALSE;
- CMDOption option[] =
- { // name, vptr, type, hasValue
- {"-reset", &bReset, COBOOL, FALSE},
- {"--reset", &bReset, COBOOL, FALSE},
- {"-r", &bReset, COBOOL, FALSE},
- };
- if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
- {
- return Status;
- }
- if (bReset)
+ ITarget* target = GetTarget();
+ if (target != nullptr)
{
- ITarget* target = GetTarget();
- if (target != nullptr)
- {
- target->Flush();
- }
- ExtOut("Internal cached state reset\n");
- return S_OK;
+ target->Flush();
}
- Target::DisplayStatus();
+ ExtOut("Internal cached state reset\n");
+ return S_OK;
}
- return Status;
+ Target::DisplayStatus();
+ return S_OK;
}
#ifndef FEATURE_PAL
size_t nArg;
if (!GetCMDOption(args,option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg!=2)
{
ExtOut("Usage: %stoken2ee module_name mdToken\n", SOSPrefix);
ExtOut(" You can pass * for module_name to search all modules.\n");
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
\**********************************************************************/
DECLARE_API(Name2EE)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("name2ee");
MINIDUMP_NOT_SUPPORTED();
StringHolder DllName, TypeName;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
ExtOut(" use * for module_name to search all loaded modules\n");
ExtOut("Examples: %sname2ee mscorlib.dll System.String.ToString\n", SOSPrefix);
ExtOut(" %sname2ee *!System.String\n", SOSPrefix);
- return Status;
+ return E_INVALIDARG;
}
int numModule;
return Status;
}
-
-DECLARE_API(PathTo)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("pathto", args);
-}
-
-
-/**********************************************************************\
-* Routine Description: *
-* *
-* This function finds all roots (on stack or in handles) for a *
-* given object. *
-* *
-\**********************************************************************/
-DECLARE_API(GCRoot)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("gcroot", args);
-}
-
-DECLARE_API(GCWhere)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("gcwhere", args);
-}
-
-
DECLARE_API(FindRoots)
{
- INIT_API_EXT();
+ INIT_API();
MINIDUMP_NOT_SUPPORTED();
if (IsDumpFile())
};
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
{"/d", &mDML, COBOOL, FALSE},
};
- if (!GetCMDOption(args,option,ARRAY_SIZE(option),NULL,0,NULL))
+ if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
+ {
sos::Throw<sos::Exception>("Failed to parse command line arguments.");
+ }
if (type != NULL) {
if (_stricmp(type, "Pinned") == 0)
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
size_t preg = 1; // by default
if (preg > 19)
{
ExtOut("Pseudo-register number must be between 0 and 19\n");
- return Status;
+ return E_INVALIDARG;
}
}
size_t nArg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (IsDumpFile())
{
ExtOut("Live debugging session required\n");
- return Status;
+ return E_INVALIDARG;
}
if (nArg < 1 || nArg > 2)
{
ExtOut("usage: StopOnException [-derived] [-create | -create2] <type name>\n");
ExtOut(" [<pseudo-register number for result>]\n");
ExtOut("ex: StopOnException -create System.OutOfMemoryException 1\n");
- return Status;
+ return E_INVALIDARG;
}
size_t preg = 1; // by default
if (preg > 19)
{
ExtOut("Pseudo-register number must be between 0 and 19\n");
- return Status;
+ return E_INVALIDARG;
}
}
return Status;
}
-/**********************************************************************\
-* Routine Description: *
-* *
-* This function finds the size of an object or all roots. *
-* *
-\**********************************************************************/
-DECLARE_API(ObjSize)
-{
- INIT_API_EXT();
- MINIDUMP_NOT_SUPPORTED();
-
- return ExecuteCommand("objsize", args);
-}
-
#ifndef FEATURE_PAL
// For FEATURE_PAL, MEMORY_BASIC_INFORMATION64 doesn't exist yet. TODO?
DECLARE_API(GCHandleLeaks)
if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
};
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if(addExpression.data != NULL || aExpression.data != NULL)
DECLARE_API(ClrStack)
{
- INIT_API();
+ INIT_API_PROBE_MANAGED("clrstack");
BOOL bAll = FALSE;
BOOL bParams = FALSE;
};
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
EnableDMLHolder dmlHolder(dml);
return TRUE;
}
-DECLARE_API( VMMap )
+DECLARE_API(VMMap)
{
INIT_API();
}
return Status;
-} // DECLARE_API( vmmap )
+}
#endif // FEATURE_PAL
DECLARE_API(SOSFlush)
{
- INIT_API_NOEE();
+ INIT_API_NOEE_PROBE_MANAGED("sosflush");
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
+ ITarget* target = GetTarget();
+ if (target != nullptr)
{
- Status = hostServices->DispatchCommand("sosflush", args);
+ target->Flush();
}
- else
- {
- ITarget* target = GetTarget();
- if (target != nullptr)
- {
- target->Flush();
- }
- ExtOut("Internal cached state reset\n");
- return S_OK;
- }
- return Status;
+ ExtOut("Internal cached state reset\n");
+ return S_OK;
}
#ifndef FEATURE_PAL
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
if (nArg != 2)
{
ExtOut("Usage: SaveModule <address> <file to save>\n");
- return Status;
+ return E_INVALIDARG;
}
if (moduleAddr == 0) {
ExtOut ("Invalid arg\n");
- return Status;
+ return E_INVALIDARG;
}
char* ptr = Location.data;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
{
- return Status;
+ return E_INVALIDARG;
}
Output::SetDebugOutputEnabled(!bOff);
size_t uiSizeOfContext,
DWORD Flags)
{
- INIT_API();
+ INIT_API_EFN();
Status = ImplementEFNStackTraceTry(client, wszTextOut, puiTextLength,
pTransitionContexts, puiTransitionContextCount,
if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL,0,NULL))
{
- return Status;
+ return E_INVALIDARG;
}
if (bVerifyManagedExcepStack)
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return E_FAIL;
+ return E_INVALIDARG;
}
if(nArg == 0)
size_t nArg;
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return E_FAIL;
+ return E_INVALIDARG;
}
if (nArg == 1 && (_stricmp(onOff.data, "On") == 0))
ULONG cbString
)
{
- INIT_API();
+ INIT_API_EFN();
ArrayHolder<WCHAR> tmpStr = new NOTHROW WCHAR[cbString];
if (tmpStr == NULL)
ULONG cchString
)
{
- INIT_API();
+ INIT_API_EFN();
return ImplementEFNGetManagedExcepStack(StackObjAddr, wszStackString, cchString);
}
ULONG cbName
)
{
- INIT_API ();
+ INIT_API_EFN();
if (!sos::IsObject(objAddr, false))
{
PULONG pOffset
)
{
- INIT_API();
+ INIT_API_EFN();
DacpObjectData objData;
LPWSTR fieldName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR));
if (!GetCMDOption(args, NULL, 0, arg, ARRAY_SIZE(arg), &nArg))
{
- return Status;
+ return E_INVALIDARG;
}
}
ULONG64 managedThread;
ULONG osThreadId,
PULONG64 pManagedThread)
{
- INIT_API();
+ INIT_API_EFN();
_ASSERTE(pManagedThread != nullptr);
*pManagedThread = 0;
size_t narg;
if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &narg))
{
- return E_FAIL;
+ return E_INVALIDARG;
}
if (narg > 0 || bNetCore || bNetFx || bNone)
{
//
DECLARE_API(SetClrPath)
{
- INIT_API_NOEE();
+ INIT_API_NODAC_PROBE_MANAGED("setclrpath");
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
+ StringHolder runtimeModulePath;
+ CMDValue arg[] =
+ {
+ {&runtimeModulePath.data, COSTRING},
+ };
+ size_t narg;
+ if (!GetCMDOption(args, nullptr, 0, arg, ARRAY_SIZE(arg), &narg))
{
- return hostServices->DispatchCommand("setclrpath", args);
+ return E_FAIL;
}
- else
+ if (narg > 0)
{
- INIT_API_EE();
-
- StringHolder runtimeModulePath;
- CMDValue arg[] =
- {
- {&runtimeModulePath.data, COSTRING},
- };
- size_t narg;
- if (!GetCMDOption(args, nullptr, 0, arg, ARRAY_SIZE(arg), &narg))
+ std::string fullPath;
+ if (!GetAbsolutePath(runtimeModulePath.data, fullPath))
{
+ ExtErr("Invalid runtime directory %s\n", fullPath.c_str());
return E_FAIL;
}
- if (narg > 0)
- {
- std::string fullPath;
- if (!GetAbsolutePath(runtimeModulePath.data, fullPath))
- {
- ExtErr("Invalid runtime directory %s\n", fullPath.c_str());
- return E_FAIL;
- }
- g_pRuntime->SetRuntimeDirectory(fullPath.c_str());
- }
- const char* runtimeDirectory = g_pRuntime->GetRuntimeDirectory();
- if (runtimeDirectory != nullptr) {
- ExtOut("Runtime module directory: %s\n", runtimeDirectory);
- }
+ g_pRuntime->SetRuntimeDirectory(fullPath.c_str());
+ }
+ const char* runtimeDirectory = g_pRuntime->GetRuntimeDirectory();
+ if (runtimeDirectory != nullptr) {
+ ExtOut("Runtime module directory: %s\n", runtimeDirectory);
}
return S_OK;
}
//
DECLARE_API(runtimes)
{
- INIT_API_NOEE();
+ INIT_API_NOEE_PROBE_MANAGED("runtimes");
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
+ BOOL bNetFx = FALSE;
+ BOOL bNetCore = FALSE;
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"-netfx", &bNetFx, COBOOL, FALSE},
+ {"-netcore", &bNetCore, COBOOL, FALSE},
+ };
+ if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
{
- Status = hostServices->DispatchCommand("runtimes", args);
+ return E_INVALIDARG;
}
- else
+ if (bNetCore || bNetFx)
{
- BOOL bNetFx = FALSE;
- BOOL bNetCore = FALSE;
- CMDOption option[] =
- { // name, vptr, type, hasValue
- {"-netfx", &bNetFx, COBOOL, FALSE},
- {"-netcore", &bNetCore, COBOOL, FALSE},
- };
- if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
- {
- return Status;
- }
- if (bNetCore || bNetFx)
- {
#ifndef FEATURE_PAL
- if (IsWindowsTarget())
- {
- PCSTR name = bNetFx ? "desktop .NET Framework" : ".NET Core";
- if (!Target::SwitchRuntime(bNetFx))
- {
- ExtErr("The %s runtime is not loaded\n", name);
- return E_FAIL;
- }
- ExtOut("Switched to %s runtime successfully\n", name);
- }
- else
-#endif
+ if (IsWindowsTarget())
+ {
+ PCSTR name = bNetFx ? "desktop .NET Framework" : ".NET Core";
+ if (!Target::SwitchRuntime(bNetFx))
{
- ExtErr("The '-netfx' and '-netcore' options are only supported on Windows targets\n");
- return E_FAIL;
+ ExtErr("The %s runtime is not loaded\n", name);
+ return E_INVALIDARG;
}
+ ExtOut("Switched to %s runtime successfully\n", name);
}
else
+#endif
{
- Target::DisplayStatus();
+ ExtErr("The '-netfx' and '-netcore' options are only supported on Windows targets\n");
+ return E_INVALIDARG;
}
}
- return Status;
-}
-
-#ifdef HOST_WINDOWS
-//
-// Sets the symbol server path.
-//
-DECLARE_API(SetSymbolServer)
-{
- INIT_API_EXT();
- return ExecuteCommand("setsymbolserver", args);
-}
-
-//
-// Dumps the managed assemblies
-//
-DECLARE_API(clrmodules)
-{
- INIT_API_EXT();
- return ExecuteCommand("clrmodules", args);
-}
-
-//
-// Dumps the Native AOT crash info
-//
-DECLARE_API(crashinfo)
-{
- INIT_API_EXT();
- return ExecuteCommand("crashinfo", args);
-}
-
-//
-// Dumps async stacks
-//
-DECLARE_API(DumpAsync)
-{
- INIT_API_EXT();
- return ExecuteCommand("dumpasync", args);
-}
-
-//
-// Enables and disables managed extension logging
-//
-DECLARE_API(logging)
-{
- INIT_API_EXT();
- return ExecuteCommand("logging", args);
-}
-
-typedef HRESULT (*PFN_COMMAND)(PDEBUG_CLIENT client, PCSTR args);
-
-//
-// Executes managed extension commands
-//
-DECLARE_API(ext)
-{
- INIT_API_EXT();
-
- 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());
- }
+ Target::DisplayStatus();
}
return Status;
}
-#endif // HOST_WINDOWS
-
void PrintHelp (__in_z LPCSTR pszCmdName)
{
static LPSTR pText = NULL;
\**********************************************************************/
DECLARE_API(Help)
{
- INIT_API_EXT();
+ INIT_API_NOEE_PROBE_MANAGED("help");
StringHolder commandName;
CMDValue arg[] =
if (nArg == 1)
{
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
- {
- if (hostServices->DisplayHelp(commandName.data) == S_OK)
- {
- return S_OK;
- }
- }
-
// Convert commandName to lower-case
LPSTR curChar = commandName.data;
while (*curChar != '\0')
else
{
PrintHelp ("contents");
- IHostServices* hostServices = GetHostServices();
- if (hostServices != nullptr)
- {
- ExtOut("\n");
- hostServices->DisplayHelp(nullptr);
- }
}
return S_OK;
BOOL IsMTForFreeObj(DWORD_PTR pMT);
-HRESULT ExecuteCommand(PCSTR commandName, PCSTR args);
-
enum ARGTYPE {COBOOL,COSIZE_T,COHEX,COSTRING};
struct CMDOption
{
PCSTR commandName,
PCSTR arguments) = 0;
- /// <summary>
- /// Displays the help for a managed extension command
- /// </summary>
- /// <param name="commandName"></param>
- /// <returns>error code</returns>
- virtual HRESULT STDMETHODCALLTYPE DisplayHelp(
- PCSTR commandName) = 0;
-
/// <summary>
/// Uninitialize the extension infrastructure
/// </summary>
g_services->AddCommand("ext", new sosCommand(nullptr), "Executes various coreclr debugging commands. Use the syntax 'sos <command - name> <args>'. For more information, see 'soshelp'.");
g_services->AddManagedCommand("analyzeoom", "Provides a stack trace of managed code only.");
g_services->AddCommand("bpmd", new sosCommand("bpmd"), "Creates a breakpoint at the specified managed method in the specified module.");
+ g_services->AddManagedCommand("assemblies", "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"), "Lists the managed threads running.");
g_services->AddCommand("histroot", new sosCommand("HistRoot"), "Displays information related to both promotions and relocations of the specified root.");
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 preceding and succeeding the specified address.");
+ g_services->AddManagedCommand("listnearobj", "Displays the object preceding and succeeding the specified address.");
g_services->AddManagedCommand("loadsymbols", "Loads the .NET Core native module symbols.");
g_services->AddManagedCommand("logging", "Enables/disables 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->AddManagedCommand("objsize", "Displays the size of the specified object.");
- g_services->AddCommand("pathto", new sosCommand("PathTo"), "Displays the GC path from <root> to <target>.");
+ g_services->AddManagedCommand("pathto", "Displays the GC path from <root> to <target>.");
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"), "Lists the runtimes in the target or change the default runtime.");
g_services->AddCommand("token2ee", new sosCommand("token2ee"), "Displays the MethodTable structure and MethodDesc structure for the specified token and module.");
g_services->AddManagedCommand("verifyheap", "Checks the GC heap for signs of corruption.");
g_services->AddManagedCommand("verifyobj", "Checks the object that is passed as an argument for signs of corruption.");
+ g_services->AddManagedCommand("traverseheap", "Writes out heap information to a file in a format understood by the CLR Profiler.");
return true;
}
_consoleService.AddCommandHistory(history);
}
catch (Exception ex) when
- (ex is IOException or
- ArgumentNullException or
- UnauthorizedAccessException or
- NotSupportedException or
- SecurityException)
+ (ex is IOException
+ or ArgumentNullException
+ or UnauthorizedAccessException
+ or NotSupportedException
+ or SecurityException)
{
}
// Add the specially handled exit command
_commandService.AddCommands(typeof(ExitCommand), (services) => new ExitCommand(_consoleService.Stop));
- // Add "sos" command manually
- _commandService.AddCommands(typeof(SOSCommand), (services) => new SOSCommand(_commandService, services));
-
// Display any extension assembly loads on console
_serviceManager.NotifyExtensionLoad.Register((Assembly assembly) => _fileLoggingConsoleService.WriteLine($"Loading extension {assembly.Location}"));
_serviceManager.NotifyExtensionLoadFailure.Register((Exception ex) => _fileLoggingConsoleService.WriteLine(ex.Message));
_serviceContainer.AddService<IConsoleFileLoggingService>(_fileLoggingConsoleService);
_serviceContainer.AddService<IDiagnosticLoggingService>(DiagnosticLoggingService.Instance);
_serviceContainer.AddService<ICommandService>(_commandService);
+ _serviceContainer.AddService<CommandService>(_commandService);
SymbolService symbolService = new(this);
_serviceContainer.AddService<ISymbolService>(symbolService);
symbolService.AddCachePath(symbolService.DefaultSymbolCache);
symbolService.AddDirectoryPath(Path.GetDirectoryName(dump_path.FullName));
- // Run the commands from the dotnet-dump command line
+ // Run the commands from the dotnet-dump command line. Any errors/exceptions from the
+ // command execution will be displayed and dotnet-dump exited.
if (command != null)
{
- foreach (string cmd in command)
+ foreach (string commandLine in command)
{
- _commandService.Execute(cmd, contextService.Services);
+ if (!_commandService.Execute(commandLine, contextService.Services))
+ {
+ throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{commandLine}'");
+ }
if (_consoleService.Shutdown)
{
break;
}
}
}
+
+ // Now start the REPL command loop if the console isn't redirected
if (!_consoleService.Shutdown && (!Console.IsOutputRedirected || Console.IsInputRedirected))
{
// Start interactive command line processing
_consoleService.Start((string prompt, string commandLine, CancellationToken cancellation) => {
_fileLoggingConsoleService.WriteLine("{0}{1}", prompt, commandLine);
- _commandService.Execute(commandLine, contextService.Services);
+ if (!_commandService.Execute(commandLine, contextService.Services))
+ {
+ throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{commandLine}'");
+ }
});
}
}
catch (Exception ex) when
- (ex is ClrDiagnosticsException or
- FileNotFoundException or
- DirectoryNotFoundException or
- UnauthorizedAccessException or
- PlatformNotSupportedException or
- InvalidDataException or
- InvalidOperationException or
- NotSupportedException)
+ (ex is ClrDiagnosticsException
+ or DiagnosticsException
+ or FileNotFoundException
+ or DirectoryNotFoundException
+ or UnauthorizedAccessException
+ or PlatformNotSupportedException
+ or InvalidDataException
+ or InvalidOperationException
+ or NotSupportedException)
{
- _fileLoggingConsoleService.WriteError($"{ex.Message}");
+ _fileLoggingConsoleService.WriteLineError($"{ex.Message}");
return Task.FromResult(1);
}
finally
File.WriteAllLines(historyFileName, _consoleService.GetCommandHistory());
}
catch (Exception ex) when
- (ex is IOException or
- UnauthorizedAccessException or
- NotSupportedException or
- SecurityException)
+ (ex is IOException
+ or UnauthorizedAccessException
+ or NotSupportedException
+ or SecurityException)
{
}
}
namespace Microsoft.Diagnostics.Tools.Dump
{
- [Command(Name = "readmemory", Aliases = new string[] { "d" }, Help = "Dumps memory contents.")]
+ [Command(Name = "d", Aliases = new string[] { "readmemory" }, Help = "Dumps memory contents.")]
[Command(Name = "db", DefaultOptions = "--ascii:true --unicode:false --ascii-string:false --unicode-string:false -c:128 -l:1 -w:16", Help = "Dumps memory as bytes.")]
[Command(Name = "dc", DefaultOptions = "--ascii:false --unicode:true --ascii-string:false --unicode-string:false -c:64 -l:2 -w:8", Help = "Dumps memory as chars.")]
[Command(Name = "da", DefaultOptions = "--ascii:false --unicode:false --ascii-string:true --unicode-string:false -c:128 -l:1 -w:0", Help = "Dumps memory as zero-terminated byte strings.")]
namespace Microsoft.Diagnostics.Tools.Dump
{
- [Command(Name = "sos", Aliases = new string[] { "ext" }, Help = "Executes various SOS debugging commands.", Flags = CommandFlags.Global | CommandFlags.Manual)]
+ [Command(Name = "sos", Aliases = new string[] { "ext" }, Help = "Executes various SOS debugging commands.")]
public class SOSCommand : CommandBase
{
- private readonly CommandService _commandService;
- private readonly IServiceProvider _services;
- private SOSHost _sosHost;
+ [ServiceImport]
+ public CommandService CommandService { get; set; }
- [Argument(Name = "arguments", Help = "SOS command and arguments.")]
+ [ServiceImport]
+ public IServiceProvider Services { get; set; }
+
+ [ServiceImport(Optional = true)]
+ public SOSHost SOSHost { get; set; }
+
+ [Argument(Name = "command_and_arguments", Help = "SOS command and arguments.")]
public string[] Arguments { get; set; }
- public SOSCommand(CommandService commandService, IServiceProvider services)
+ public SOSCommand()
{
- _commandService = commandService;
- _services = services;
}
public override void Invoke()
{
- string commandLine;
- string commandName;
+ string command;
+ string arguments;
if (Arguments != null && Arguments.Length > 0)
{
- commandLine = string.Concat(Arguments.Select((arg) => arg + " ")).Trim();
- commandName = Arguments[0];
+ command = Arguments[0];
+ arguments = string.Concat(Arguments.Skip(1).Select((arg) => arg + " ")).Trim();
}
else
{
- commandLine = commandName = "help";
+ command = "help";
+ arguments = null;
}
- if (_commandService.IsCommand(commandName))
+ if (CommandService.Execute(command, arguments, Services))
{
- try
- {
- _commandService.Execute(commandLine, _services);
- return;
- }
- catch (CommandNotSupportedException)
- {
- }
+ return;
}
- if (_sosHost is null)
+ if (SOSHost is null)
{
- _sosHost = _services.GetService<SOSHost>();
- if (_sosHost is null)
- {
- throw new DiagnosticsException($"'{commandName}' command not found");
- }
+ throw new CommandNotFoundException($"{CommandNotFoundException.NotFoundMessage} '{command}'");
}
- _sosHost.ExecuteCommand(commandLine);
+ SOSHost.ExecuteCommand(command, arguments);
}
}
}
--- /dev/null
+using Microsoft.Diagnostics.DebugServices.Implementation;
+using Microsoft.Diagnostics.TestHelpers;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Extensions;
+
+[assembly: SuppressMessage("Performance", "CA1825:Avoid zero-length array allocations.", Justification = "<Pending>")]
+
+namespace Microsoft.Diagnostics.DebugServices.UnitTests
+{
+ public class CommandServiceTests : IDisposable
+ {
+ private const string ListenerName = "CommandServiceTests";
+
+ private static IEnumerable<object[]> _configurations;
+
+ /// <summary>
+ /// Get the first test asset dump. It doesn't matter which one.
+ /// </summary>
+ /// <returns></returns>
+ public static IEnumerable<object[]> GetConfiguration()
+ {
+ return _configurations ??= TestRunConfiguration.Instance.Configurations
+ .Where((config) => config.AllSettings.ContainsKey("DumpFile"))
+ .Take(1)
+ .Select(c => new[] { c })
+ .ToImmutableArray();
+ }
+
+ ITestOutputHelper Output { get; set; }
+
+ public CommandServiceTests(ITestOutputHelper output)
+ {
+ Output = output;
+ LoggingListener.EnableListener(output, ListenerName);
+ }
+
+ void IDisposable.Dispose() => Trace.Listeners.Remove(ListenerName);
+
+ [SkippableTheory, MemberData(nameof(GetConfiguration))]
+ public void CommandServiceTest1(TestConfiguration config)
+ {
+ using TestDump testDump = new(config);
+
+ CaptureConsoleService consoleService = new();
+ testDump.ServiceContainer.AddService<IConsoleService>(consoleService);
+
+ CommandService commandService = new();
+ testDump.ServiceContainer.AddService<ICommandService>(commandService);
+
+ // Add all the test commands
+ commandService.AddCommands(typeof(TestCommand1).Assembly);
+
+ // See if the test commands exists
+ Assert.Contains(commandService.Commands, ((string name, string help, IEnumerable<string> aliases) cmd) => cmd.name == "testcommand");
+
+ // Invoke only TestCommand1
+ TestCommand1.FilterValue = true;
+ TestCommand1.Invoked = false;
+ TestCommand2.FilterValue = false;
+ TestCommand2.Invoked = false;
+ TestCommand3.FilterValue = false;
+ TestCommand3.Invoked = false;
+ Assert.True(commandService.Execute("testcommand", testDump.Target.Services));
+ Assert.True(TestCommand1.Invoked);
+ Assert.False(TestCommand2.Invoked);
+ Assert.False(TestCommand3.Invoked);
+
+ // Check for TestCommand1 help
+ string help1 = commandService.GetDetailedHelp("testcommand", testDump.Target.Services, consoleWidth: int.MaxValue);
+ Assert.NotNull(help1);
+ Output.WriteLine(help1);
+ Assert.Contains("Test command #1", help1);
+
+ // Invoke only TestCommand2
+ TestCommand1.FilterValue = false;
+ TestCommand1.Invoked = false;
+ TestCommand2.FilterValue = true;
+ TestCommand2.Invoked = false;
+ TestCommand3.FilterValue = false;
+ TestCommand3.Invoked = false;
+ Assert.True(commandService.Execute("testcommand", testDump.Target.Services));
+ Assert.False(TestCommand1.Invoked);
+ Assert.True(TestCommand2.Invoked);
+ Assert.False(TestCommand3.Invoked);
+
+ // Invoke only TestCommand3
+
+ TestCommand1.FilterValue = false;
+ TestCommand1.Invoked = false;
+ TestCommand2.FilterValue = false;
+ TestCommand2.Invoked = false;
+ TestCommand3.FilterValue = true;
+ TestCommand3.Invoked = false;
+ Assert.True(commandService.Execute("testcommand", "--foo 23", testDump.Target.Services));
+ Assert.False(TestCommand1.Invoked);
+ Assert.False(TestCommand2.Invoked);
+ Assert.True(TestCommand3.Invoked);
+
+ // Check for TestCommand3 help
+ string help3 = commandService.GetDetailedHelp("testcommand", testDump.Target.Services, consoleWidth: int.MaxValue);
+ Assert.NotNull(help3);
+ Output.WriteLine(help3);
+ Assert.Contains("Test command #3", help3);
+
+ // Invoke none of the test commands
+ TestCommand1.FilterValue = false;
+ TestCommand1.Invoked = false;
+ TestCommand2.FilterValue = false;
+ TestCommand2.Invoked = false;
+ TestCommand3.FilterValue = false;
+ TestCommand3.Invoked = false;
+ try
+ {
+ Assert.False(commandService.Execute("testcommand", testDump.Target.Services));
+ }
+ catch (DiagnosticsException ex)
+ {
+ Assert.Matches("Test command #2 filter", ex.Message);
+ }
+ Assert.False(TestCommand1.Invoked);
+ Assert.False(TestCommand2.Invoked);
+ Assert.False(TestCommand3.Invoked);
+ }
+ }
+}
public static IEnumerable<object[]> GetConfigurations()
{
- _configurations ??= TestRunConfiguration.Instance.Configurations
+ return _configurations ??= TestRunConfiguration.Instance.Configurations
.Where((config) => config.AllSettings.ContainsKey("DumpFile"))
- .Select((config) => CreateHost(config))
- .Select((host) => new[] { host }).ToImmutableArray();
- return _configurations;
+ .Select(CreateHost)
+ .Select((host) => new[] { host })
+ .ToImmutableArray();
}
private static TestHost CreateHost(TestConfiguration config)
}
Assert.NotNull(module);
+ if (OS.Kind != OSKind.Windows)
+ {
+ // Skip managed modules when running on Linux/OSX because of the 6.0 injection activation issue in the DAC
+ if (moduleData.TryGetValue("IsManaged", out bool isManaged) && isManaged)
+ {
+ continue;
+ }
+ }
+
if (host.Target.Host.HostType != HostType.Lldb)
{
// Check that the resulting module matches the test data
{
throw new SkipTestException("Not supported on Alpine Linux");
}
+ // Disable running on Linux/OSX because of the 6.0 injection activation issue in the DAC
+ if (OS.Kind != OSKind.Windows)
+ {
+ throw new SkipTestException("Not supported on Linux");
+ }
IRuntimeService runtimeService = host.Target.Services.GetService<IRuntimeService>();
Assert.NotNull(runtimeService);
--- /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 Xunit;
+
+namespace Microsoft.Diagnostics.DebugServices.UnitTests
+{
+ [Command(Name = "testcommand", Help = "Test command #1")]
+ public class TestCommand1 : CommandBase
+ {
+ public static bool FilterValue;
+ public static bool Invoked;
+
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Argument(Name = "FileName", Help = "Test argument.")]
+ public string FileName { get; set; }
+
+ public override void Invoke()
+ {
+ Assert.NotNull(Target);
+ Invoked = true;
+ }
+
+ [FilterInvoke]
+ public bool FilterInvoke() => FilterValue;
+ }
+
+ [Command(Name = "testcommand", Help = "Test command #2")]
+ public class TestCommand2 : CommandBase
+ {
+ public static bool FilterValue;
+ public static bool Invoked;
+
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Option(Name = "--foo", Help = "Test option.")]
+ public int Foo { get; set; }
+
+ public override void Invoke()
+ {
+ Assert.NotNull(Target);
+ Invoked = true;
+ }
+
+ [FilterInvoke(Message = "Test command #2 filter")]
+ public bool FilterInvoke() => FilterValue;
+ }
+
+ [Command(Name = "testcommand", Help = "Test command #3")]
+ public class TestCommand3 : CommandBase
+ {
+ public static bool FilterValue;
+ public static bool Invoked;
+
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Option(Name = "--foo", Help = "Test option.")]
+ public int Foo { get; set; }
+
+ public override void Invoke()
+ {
+ Assert.NotNull(Target);
+ Invoked = true;
+ }
+
+ [FilterInvoke]
+ public static bool FilterInvoke() => FilterValue;
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Diagnostics.DebugServices;
+
+namespace TestExtension
+{
+ [Command(Name = "clrstack", Help = "Test command #1")]
+ public class TestCommand1 : CommandBase
+ {
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Argument(Name = "FileName", Help = "Test argument.")]
+ public string FileName { get; set; }
+
+ public override void Invoke()
+ {
+ if (Target is null)
+ {
+ throw new ArgumentNullException(nameof(Target));
+ }
+ WriteLine("Test command #1 invoked");
+ }
+
+ [FilterInvoke]
+ public bool FilterInvoke() => true;
+ }
+
+ [Command(Name = "dumpheap", Help = "Test command #2")]
+ public class TestCommand2 : CommandBase
+ {
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Option(Name = "--foo", Help = "Test option.")]
+ public int Foo { get; set; }
+
+ public override void Invoke()
+ {
+ if (Target is null)
+ {
+ throw new ArgumentNullException(nameof(Target));
+ }
+ WriteLine("Test command #2 invoked");
+ }
+
+ [FilterInvoke]
+ public bool FilterInvoke() => true;
+ }
+
+ [Command(Name = "dumpheap", Help = "Test command #3")]
+ public class TestCommand3 : CommandBase
+ {
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Option(Name = "--foo", Help = "Test option.")]
+ public int Foo { get; set; }
+
+ public override void Invoke()
+ {
+ if (Target is null)
+ {
+ throw new ArgumentNullException(nameof(Target));
+ }
+ WriteLine("Test command #3 invoked");
+ }
+
+ [FilterInvoke]
+ public bool FilterInvoke() => false;
+ }
+
+ [Command(Name = "assemblies", Help = "Test command #4")]
+ public class TestCommand4 : CommandBase
+ {
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Option(Name = "--foo", Help = "Test option.")]
+ public int Foo { get; set; }
+
+ public override void Invoke()
+ {
+ if (Target is null)
+ {
+ throw new ArgumentNullException(nameof(Target));
+ }
+ WriteLine("Test command #4 invoked");
+ }
+
+ [FilterInvoke]
+ public static bool FilterInvoke() => true;
+ }
+
+ [Command(Name = "ip2md", Help = "Test command #5")]
+ public class TestCommand5 : CommandBase
+ {
+ [ServiceImport]
+ public ITarget Target { get; set; }
+
+ [Option(Name = "--bar", Help = "Test option #5.")]
+ public int Foo { get; set; }
+
+ public override void Invoke()
+ {
+ if (Target is null)
+ {
+ throw new ArgumentNullException(nameof(Target));
+ }
+ WriteLine("Test command #5 invoked");
+ }
+
+ [FilterInvoke]
+ public static bool FilterInvoke() => false;
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.DebugServices\Microsoft.Diagnostics.DebugServices.csproj" />
+ </ItemGroup>
+
+</Project>