Skip to content

Commit

Permalink
Add interactive dump "analyze" dump support to dotnet-dump project.
Browse files Browse the repository at this point in the history
Use the System.CommandLine CommandProcessor for the new commands.

Add "sos", "exit", "help", native "modules" and "setthread" commands.
  • Loading branch information
mikem8361 committed Feb 22, 2019
1 parent 4cb670b commit 6d59be7
Show file tree
Hide file tree
Showing 12 changed files with 529 additions and 84 deletions.
77 changes: 77 additions & 0 deletions src/Tools/dotnet-dump/AnalyzeContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// --------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// --------------------------------------------------------------------
using Microsoft.Diagnostics.Runtime;
using SOS;
using System;
using System.CommandLine;
using System.Threading;

namespace Microsoft.Diagnostic.Tools.Dump
{
/// <summary>
/// The the common context for analyze commands
/// </summary>
public class AnalyzeContext: ISOSHostContext
{
readonly IConsole _console;
ClrRuntime _runtime;

public AnalyzeContext(IConsole console, DataTarget target, Action exit)
{
_console = console;
Target = target;
Exit = exit;
}

/// <summary>
/// ClrMD data target
/// </summary>
public DataTarget Target { get; }

/// <summary>
/// ClrMD runtime info
/// </summary>
public ClrRuntime Runtime
{
get
{
if (_runtime == null)
{
if (Target.ClrVersions.Count != 1)
{
throw new InvalidOperationException("More or less than 1 CLR version is present");
}
_runtime = Target.ClrVersions[0].CreateRuntime();
}
return _runtime;
}
}

/// <summary>
/// Delegate to invoke to exit repl
/// </summary>
public Action Exit { get; }

/// <summary>
/// Current OS thread Id
/// </summary>
public int CurrentThreadId { get; set; }

/// <summary>
/// Cancellation token for current command
/// </summary>
public CancellationToken CancellationToken { get; set; }

/// <summary>
/// Console write function
/// </summary>
/// <param name="text"></param>
void ISOSHostContext.Write(string text)
{
_console.Out.Write(text);
}
}
}
72 changes: 72 additions & 0 deletions src/Tools/dotnet-dump/Analyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.Diagnostic.Repl;
using Microsoft.Diagnostics.Runtime;
using System.CommandLine;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostic.Tools.Dump
{
public class Analyzer
{
private readonly ConsoleProvider _consoleProvider;
private readonly CommandProcessor _commandProcessor;

public Analyzer()
{
_consoleProvider = new ConsoleProvider();
_commandProcessor = new CommandProcessor(new Assembly[] { typeof(Analyzer).Assembly });
}

public async Task<int> Analyze(FileInfo dump_path, string[] command)
{
_consoleProvider.Out.WriteLine($"Loading core dump: {dump_path} ...");

DataTarget target = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
target = DataTarget.LoadCoreDump(dump_path.FullName);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
target = DataTarget.LoadCrashDump(dump_path.FullName, CrashDumpReader.ClrMD);
}
else {
_consoleProvider.Error.WriteLine($"{RuntimeInformation.OSDescription} not supported");
return 1;
}

using (target)
{
// Create common analyze context for commands
var analyzeContext = new AnalyzeContext(_consoleProvider, target, _consoleProvider.Stop) {
CurrentThreadId = unchecked((int)target.DataReader.EnumerateAllThreads().FirstOrDefault())
};
_commandProcessor.CommandContext = analyzeContext;

// Automatically enable symbol server support on Linux and MacOS
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
await _commandProcessor.Parse("setsymbolserver -ms", _consoleProvider);
}

// Run the commands from the dotnet-dump command line
if (command != null)
{
foreach (string cmd in command)
{
await _commandProcessor.Parse(cmd, _consoleProvider);
}
}

// Start interactive command line processing
await _consoleProvider.Start(async (string commandLine, CancellationToken cancellation) => {
analyzeContext.CancellationToken = cancellation;
await _commandProcessor.Parse(commandLine, _consoleProvider);
});
}

return 0;
}
}
}
19 changes: 19 additions & 0 deletions src/Tools/dotnet-dump/Commands/ExitCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

using Microsoft.Diagnostic.Repl;
using System.CommandLine;
using System.Threading.Tasks;

namespace Microsoft.Diagnostic.Tools.Dump
{
[Command(Name = "exit", Help = "Exit interactive mode.")]
public class ExitCommand : CommandBase
{
public AnalyzeContext AnalyzeContext { get; set; }

public override Task InvokeAsync()
{
AnalyzeContext.Exit();
return Task.CompletedTask;
}
}
}
36 changes: 36 additions & 0 deletions src/Tools/dotnet-dump/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.Diagnostic.Repl;
using System.CommandLine;
using System.Threading.Tasks;

namespace Microsoft.Diagnostic.Tools.Dump
{
[Command(Name = "help", Help = "Display help for a command.")]
public class HelpCommand : CommandBase
{
[Argument(Help = "Command to find help.")]
public string Command { get; set; }

public CommandProcessor CommandProcessor { get; set; }

public override Task InvokeAsync()
{
return Task.CompletedTask;
}

/// <summary>
/// Get help builder interface
/// </summary>
/// <param name="helpBuilder">help builder</param>
public Task InvokeAsync(IHelpBuilder helpBuilder)
{
Command command = CommandProcessor.GetCommand(Command);
if (command != null) {
helpBuilder.Write(command);
}
else {
Console.Error.WriteLine($"Help for {Command} not found.");
}
return Task.CompletedTask;
}
}
}
44 changes: 44 additions & 0 deletions src/Tools/dotnet-dump/Commands/ModulesCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

using Microsoft.Diagnostic.Repl;
using Microsoft.Diagnostics.Runtime;
using System.CommandLine;
using System.Linq;
using System.Threading.Tasks;

namespace Microsoft.Diagnostic.Tools.Dump
{
[Command(Name = "modules", Help = "Displays the native modules in the process.")]
[Command(Name = "lm")]
public class ModulesCommand : CommandBase
{
[Option(Name = "--verbose", Help = "Displays more details.")]
[OptionAlias(Name = "-v")]
public bool Verbose { get; set; }

public AnalyzeContext AnalyzeContext { get; set; }

public override Task InvokeAsync()
{
foreach (ModuleInfo module in AnalyzeContext.Target.DataReader.EnumerateModules())
{
if (Verbose)
{
WriteLine("{0}", module.FileName);
WriteLine(" Address: {0:X16}", module.ImageBase);
WriteLine(" FileSize: {0:X8}", module.FileSize);
WriteLine(" TimeStamp: {0:X8}", module.TimeStamp);
if (module.BuildId != null) {
WriteLine(" BuildId: {0}", string.Concat(module.BuildId.Select((b) => b.ToString("x2"))));
}
WriteLine(" IsRuntime: {0}", module.IsRuntime);
WriteLine(" IsManaged: {0}", module.IsManaged);
}
else
{
WriteLine("{0:X16} {1:X8} {2}", module.ImageBase, module.FileSize, module.FileName);
}
}
return Task.CompletedTask;
}
}
}
70 changes: 70 additions & 0 deletions src/Tools/dotnet-dump/Commands/SOSCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.Diagnostic.Repl;
using SOS;
using System;
using System.CommandLine;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Diagnostic.Tools.Dump
{
[Command(Name = "clrstack", AliasExpansion = "ClrStack", Help = "Provides a stack trace of managed code only.")]
[Command(Name = "clrthreads", AliasExpansion = "Threads", Help = "List the managed threads running.")]
[Command(Name = "dumpasync", AliasExpansion = "DumpAsync", Help = "Displays info about async state machines on the garbage-collected heap.")]
[Command(Name = "dumpclass", AliasExpansion = "DumpClass", Help = "Displays information about a EE class structure at the specified address.")]
[Command(Name = "dumpdelegate", AliasExpansion = "DumpDelegate", Help = "Displays information about a delegate.")]
[Command(Name = "dumpdomain", AliasExpansion = "DumpDomain", Help = "Displays information all the AppDomains and all assemblies within the domains.")]
[Command(Name = "dumpheap", AliasExpansion = "DumpHeap", Help = "Displays info about the garbage-collected heap and collection statistics about objects.")]
[Command(Name = "dumpil", AliasExpansion = "DumpIL", Help = "Displays the Microsoft intermediate language (MSIL) that is associated with a managed method.")]
[Command(Name = "dumplog", AliasExpansion = "DumpLog", Help = "Writes the contents of an in-memory stress log to the specified file.")]
[Command(Name = "dumpmd", AliasExpansion = "DumpMD", Help = "Displays information about a MethodDesc structure at the specified address.")]
[Command(Name = "dumpmodule", AliasExpansion = "DumpModule", Help = "Displays information about a EE module structure at the specified address.")]
[Command(Name = "dumpmt", AliasExpansion = "DumpMT", Help = "Displays information about a method table at the specified address.")]
[Command(Name = "dumpobj", AliasExpansion = "DumpObj", Help = "Displays info about an object at the specified address.")]
[Command(Name = "dumpstack", AliasExpansion = "DumpStack", Help = "Displays a native and managed stack trace.")]
[Command(Name = "dso", AliasExpansion = "DumpStackObjects", Help = "Displays all managed objects found within the bounds of the current stack.")]
[Command(Name = "eeheap", AliasExpansion = "EEHeap", Help = "Displays info about process memory consumed by internal runtime data structures.")]
[Command(Name = "eestack", AliasExpansion = "EEStack", Help = "Runs dumpstack on all threads in the process.")]
[Command(Name = "finalizequeue", AliasExpansion = "FinalizeQueue", Help = "Displays all objects registered for finalization.")]
[Command(Name = "gcroot", AliasExpansion = "GCRoot", Help = "Displays info about references (or roots) to an object at the specified address.")]
[Command(Name = "ip2md", AliasExpansion = "IP2MD", Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")]
[Command(Name = "name2ee", AliasExpansion = "Name2EE", Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")]
[Command(Name = "pe", AliasExpansion = "PrintException", Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")]
[Command(Name = "syncblk", AliasExpansion = "SyncBlk", Help = "Displays the SyncBlock holder info.")]
[Command(Name = "histclear", AliasExpansion = "HistClear", Help = "Releases any resources used by the family of Hist commands.")]
[Command(Name = "histinit", AliasExpansion = "HistInit", Help = "Initializes the SOS structures from the stress log saved in the debuggee.")]
[Command(Name = "histobj", AliasExpansion = "HistObj", Help = "Examines all stress log relocation records and displays the chain of garbage collection relocations that may have led to the address passed in as an argument.")]
[Command(Name = "histobjfind", AliasExpansion = "HistObjFind", Help = "Displays all the log entries that reference an object at the specified address.")]
[Command(Name = "histroot", AliasExpansion = "HistRoot", Help = "Displays information related to both promotions and relocations of the specified root.")]
[Command(Name = "setsymbolserver", AliasExpansion = "SetSymbolServer", Help = "Enables the symbol server support ")]
[Command(Name = "soshelp", AliasExpansion = "Help", Help = "Displays all available commands when no parameter is specified, or displays detailed help information about the specified command. soshelp <command>")]
public class SOSCommand : CommandBase
{
[Argument(Name = "arguments", Help = "Arguments to SOS command.")]
public string[] Arguments { get; set; }

public AnalyzeContext AnalyzeContext { get; set; }

private SOSHost _sosHost;

public override Task InvokeAsync()
{
try {
if (_sosHost == null) {
_sosHost = new SOSHost(AnalyzeContext.Target.DataReader, AnalyzeContext);
}
string arguments = null;
if (Arguments.Length > 0) {
arguments = string.Concat(Arguments.Select((arg) => arg + " "));
}
_sosHost.ExecuteCommand(AliasExpansion, arguments);
}
catch (Exception ex) when (ex is FileNotFoundException || ex is EntryPointNotFoundException || ex is InvalidOperationException) {
Console.Error.WriteLine(ex.Message);
}
return Task.CompletedTask;
}
}
}
35 changes: 35 additions & 0 deletions src/Tools/dotnet-dump/Commands/SetThreadCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

using Microsoft.Diagnostic.Repl;
using System.CommandLine;
using System.Threading.Tasks;

namespace Microsoft.Diagnostic.Tools.Dump
{
[Command(Name = "setthread", Help = "Sets or displays the current thread id for the SOS commands.")]
[Command(Name = "threads")]
public class SetThreadCommand : CommandBase
{
[Argument(Help = "The thread id to set, otherwise displays the current id.")]
public int? ThreadId { get; set; } = null;

public AnalyzeContext AnalyzeContext { get; set; }

public override Task InvokeAsync()
{
if (ThreadId.HasValue)
{
AnalyzeContext.CurrentThreadId = ThreadId.Value;
}
else
{
int index = 0;
foreach (uint threadId in AnalyzeContext.Target.DataReader.EnumerateAllThreads())
{
WriteLine("{0}{1} 0x{2:X4} ({2})", threadId == AnalyzeContext.CurrentThreadId ? "*" : " ", index, threadId);
index++;
}
}
return Task.CompletedTask;
}
}
}
Loading

0 comments on commit 6d59be7

Please sign in to comment.