* [FreeBSD Instructions](documentation/lldb/freebsd-instructions.md)
* [NetBSD Instructions](documentation/lldb/netbsd-instructions.md)
+## Installing SOS
+
+* [Linux and MacOS Instructions](documentation/installing-sos-instructions.md)
+* [Windows Instructions](documentation/installing-sos-windows-instructions.md)
+
## Using SOS
* [SOS debugging for Linux/MacOS](documentation/sos-debugging-extension.md)
For analyzing managed memory leaks over time, the investigator first wants to capture a series of dumps that will show the memory growth.
- > dotnet tool install -g dotnet-dump
+ $ dotnet tool install -g dotnet-dump
You can invoke the tool using the following command: dotnet-dump
Tool 'dotnet-dump' (version '1.0.0') was successfully installed.
- > dotnet dump --process-id 1902 --number 2
- Writing: ./core0001
+
+ $ dotnet dump collect --process-id 1902
+ Writing minidump with heap to file ./core_20190226_135837
+ Written 98983936 bytes (24166 pages) to core file
+ Complete
-... 10 seconds pass (the default time interval)
+Some time interval passes
- > dotnet dump --process-id 1902 --number 2
- Writing: ./core0001
- Writing: ./core0002
+ $ dotnet dump collect --process-id 1902
+ Writing minidump with heap to file ./core_20190226_135850
+ Written 98959360 bytes (24160 pages) to core file
Complete
Next the investigator needs to compare the heaps in these two dumps.
- > dotnet dump analyze ./core0002
- Type 'help' for help
- $ GCHeapDiff ./core0001
+ > dotnet dump analyze ./core_20190226_135850
+ Loading core dump: ./core_20190226_135850
+ $ gcheapdiff ./core_20190226_135837
Showing top GC heap differences by size
Type Current Heap Baseline Heap Delta
Size / Count Size / Count Size / Count
WebApp1.RequestEntry 1800 / 180 1200 / 120 + 600 / + 60
...
- To show all differences use 'HeapDiff -all ./core0001'
- To show objects of a particular type use DumpHeap -type <type_name>
+ To show all differences use 'gcheapdiff -all ./core_20190226_135850'
+ To show objects of a particular type use dumpheap -type <type_name>
- $ DumpHeap -type System.String
+ $ dumpheap -type System.String
Address MT Size
03b51454 725ef698 84
03b522d4 725ef698 52
32cac6c4 725eeb40 74
...
- $ GCRoot 03b51454
+ $ gcroot 03b51454
Thread 41a0:
0ad2f274 55f99590 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
ebp-c: 0ad2f2b0
Found 1 unique roots (run 'GCRoot -all' to see all roots).
-
First we compared the leaky dump to the baseline dump to determine which types were growing, then listed addresses of particular instances of the leaking type, then determined the chain of references that was keeping that instance alive. The investigator may need to sample several instances of the leaked type to identify which ones are expected to be on the heap and which are not.
-Note: The DumpHeap/GCRoot output is identical to SOS. I'm not convinced this output is ideal for clarity, but I am not proposing we change it at this time.
+Note: The dumpheap/gcroot output is identical to SOS. I'm not convinced this output is ideal for clarity, but I am not proposing we change it at this time.
### Install SOS for use with LLDB
COMMANDS
- collect Capture one or more dumps (core files on Mac/Linux) from a process
+ collect Capture dumps from a process
analyze Starts an interactive shell with debugging commands to explore a dump
COLLECT
dotnet-dump collect -p|--process-id <pid>
- [-h|--help]
- [--interval-sec <seconds>]
- [--number <number_of_dumps>]
- [-o|--output <output_dump_path>]
- [--type <dump_type>]
+ [-h|--help]
+ [-o|--output <output_dump_path>]
+ [--type <dump_type>]
Capture one or more dumps (core files on Mac/Linux) from a process
-p, --process-id
- The process to collect dumps from
-
+ The process to collect a memory dump from.
+
-h, --help
Show command line help
- --interval-sec
- The number of seconds to wait between collecting each dump. Defaults to 10 seconds if not specified.
-
- --number
- The number of dumps to collect from the target process. Defaults to 1 if not specified.
-
-o, --output
- The path where collected dumps should be written. Defaults to .\dumpNNNN.dmp on windows and ./coreNNNN on
- Linux\Mac if not specified. NNNN is an increasing 4 digit counter for each dump, for example
- .\dump0003.dmp. If the output_dump_path specifies a directory then dump files are written to that directory
- with the same dumpNNNN[.dmp] naming format. Specifying an exact filename is only permitted when capturing a
- single dump.
+ The path where collected dumps should be written. Defaults to '.\dump_YYYYMMDD_HHMMSS.dmp' on Windows and
+ './core_YYYYMMDD_HHMMSS' on Linux where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full
+ path and file name of the dump.
--type
The dump type determines the kinds of information that are collected from the process. There are two types:
Examples:
- > dotnet-dump collect --process-id 1902 --number 2 --type mini --output ~/dumps/go/here/
- Writing: ~/dumps/go/here/core0001
-
-... 10 seconds pass (the default time interval)
-
- > dotnet-dump collect --process-id 1902 --number 2 --type mini --output ~/dumps/go/here/
- Writing: ~/dumps/go/here/core0001
- Writing: ~/dumps/go/here/core0002
- Complete
+ $ dotnet dump collect --process-id 1902 --type mini
+ Writing minidump to file ./core_20190226_135837
+ Written 98983936 bytes (24166 pages) to core file
+ Complete
+
+ $ dotnet dump collect --process-id 1902 --type mini
+ Writing minidump to file ./core_20190226_135850
+ Written 98959360 bytes (24160 pages) to core file
+ Complete
ANALYZE
Starts an interactive shell with debugging commands to explore a dump
- -h, --help
- Show command line help
-
dump_path
The dump to analyze
Examples:
- > dotnet-dump analyze core0002
- Use 'help' for help, 'q' to quit
- $
-
- ... use the nested command-line. The commands are broken out in the following section
-
+ $ dotnet-dump analyze ./core_20190226_135850
+ Loading core dump: ./core_20190226_135850
+ Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.
+ Type 'quit' or 'exit' to exit the session.
+ >
+ ... use the nested command-line. The commands are broken out in the following section
## dotnet-dump analyze nested command syntax ##
-By default these commands should come from SOS and include at least Help, DumpHeap, DumpObject, DumpArray, and PrintException. If we can get more easily we should. In addition new commands are listed below:
+By default these commands should come from SOS and include at least help, dumpheap, dumpobject, dumparray, and printexception. If we can get more easily we should. In addition new commands are listed below:
GCHEAPDIFF
- GCHeapDiff <path_to_baseline_dump>
+ gcheapdiff <path_to_baseline_dump>
Compares the current GC heap to the one contained in the baseline dump
The path to another dump that contains the baseline
Examples:
- $ GCHeapDiff ./core0001
+ $ gcheapdiff ./core_20190226_135837
Showing top GC heap differences by size
Type Current Heap Baseline Heap Delta
WebApp1.RequestEntry 1800 / 180 1200 / 120 + 600 / + 60
...
- To show all differences use 'HeapDiff -all ./core0001'
+ To show all differences use 'gcheapdiff -all ./core_20190226_135837'
To show objects of a particular type use DumpHeap -type <type_name>
## dotnet-sos ##
CPU threshold at which to create a dump of the process.
-cl
CPU threshold below which to create a dump of the process.
- -d
+ -
+d
Invoke the minidump callback routine named MiniDumpCallbackRoutine of the specified DLL.
-e
Write a dump when the process encounters an unhandled exception. Include the 1 to create dump on first chance exceptions.
### LTTNG
-TODO
\ No newline at end of file
+TODO
--- /dev/null
+Installing SOS on Linux and MacOS
+=================================
+
+The first step is to install the dotnet-sos CLI global tool. This requires the 2.1 .NET Core SDK to be installed.
+
+ $ dotnet tool install -g dotnet-sos --version 1.0.2-preview3.19151.2 --add-source https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json
+ You can invoke the tool using the following command: dotnet-sos
+ Tool 'dotnet-sos' (version '1.0.2-preview3.19151.2') was successfully installed.
+
+The next step is use this global tool to install SOS.
+
+ $ dotnet sos install
+ Installing SOS to /home/mikem/.dotnet/sos from /home/mikem/.dotnet/tools/.store/dotnet-sos/1.0.2-preview3.19151.2/dotnet-sos/1.0.2-preview3.19151.2/tools/netcoreapp2.1/any/linux-x64
+ Creating installation directory...
+ Copying files...
+ Updating existing /home/mikem/.lldbinit file - LLDB will load SOS automatically at startup
+ SOS install succeeded
+
+Now any time you run lldb, SOS will automatically be loaded and the symbol downloading enabled. This requires at least lldb 3.9 installed. See [Getting lldb](../README.md) section.
+
+ $ lldb
+ (lldb) soshelp
+ -------------------------------------------------------------------------------
+ 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 "soshelp <functionname>" for detailed info on that function.
+
+ Object Inspection Examining code and stacks
+ ----------------------------- -----------------------------
+ DumpObj (dumpobj) Threads (clrthreads)
+ DumpArray ThreadState
+ DumpAsync (dumpasync) IP2MD (ip2md)
+ DumpDelegate (dumpdelegate) u (clru)
+ DumpStackObjects (dso) DumpStack (dumpstack)
+ DumpHeap (dumpheap) EEStack (eestack)
+ DumpVC CLRStack (clrstack)
+ FinalizeQueue (finalizequeue) GCInfo
+ GCRoot (gcroot) EHInfo
+ PrintException (pe) bpmd (bpmd)
+
+ Examining CLR data structures Diagnostic Utilities
+ ----------------------------- -----------------------------
+ DumpDomain (dumpdomain) VerifyHeap
+ EEHeap (eeheap) FindAppDomain
+ Name2EE (name2ee) DumpLog (dumplog)
+ SyncBlk (syncblk)
+ DumpMT (dumpmt)
+ DumpClass (dumpclass)
+ DumpMD (dumpmd)
+ Token2EE
+ DumpModule (dumpmodule)
+ DumpAssembly
+ DumpRuntimeTypes
+ DumpIL (dumpil)
+ DumpSig
+ DumpSigElem
+
+ Examining the GC history Other
+ ----------------------------- -----------------------------
+ HistInit (histinit) SetHostRuntime (sethostruntime)
+ HistRoot (histroot) SetSymbolServer (setsymbolserver, loadsymbols)
+ HistObj (histobj) FAQ
+ HistObjFind (histobjfind) SOSFlush
+ HistClear (histclear) Help (soshelp)
+ (lldb)
+
+## Updating SOS
+
+ $ dotnet tool update -g dotnet-sos
+
+The installer needs to be run again:
+
+ $ dotnet sos install
+ Installing SOS to /home/mikem/.dotnet/sos from /home/mikem/.dotnet/tools/.store/dotnet-sos/1.0.2-preview3.19151.2/dotnet-sos/1.0.2-preview3.19151.2/tools/netcoreapp2.1/any/linux-x64
+ Installing over existing installation...
+ Creating installation directory...
+ Copying files...
+ Updating existing /home/mikem/.lldbinit file - LLDB will load SOS automatically at startup
+ Cleaning up...
+ SOS install succeeded
+
+## Uninstalling SOS
+
+To uninstall and remove the lldb configuration run this command:
+
+ $ dotnet sos uninstall
+ Uninstalling SOS from /home/mikem/.dotnet/sos
+ Reverting /home/mikem/.lldbinit file - LLDB will no longer load SOS at startup
+ SOS uninstall succeeded
+
+To remove the SOS installer global tool (optional):
+
+ $ dotnet tool uninstall -g dotnet-sos
+ Tool 'dotnet-sos' (version '1.0.2-preview3.19151.2') was successfully uninstalled.
--- /dev/null
+Installing SOS on Windows
+=========================
+
+SOS will automatically be loaded from the internal Microsoft extension gallery. You need at least version 10.0.18317.1001 or greater of the Windows debugger (windbg or cdb). SOS will load when the "coreclr.dll" module is loaded.
+
+ "C:\Program Files\Debugging Tools for Windows (x64)\cdb.exe" dotnet SymbolTestApp2.dll
+
+ Microsoft (R) Windows Debugger Version 10.0.18317.1001 AMD64
+ Copyright (c) Microsoft Corporation. All rights reserved.
+
+ 0:000> sxe ld coreclr
+ 0:000> g
+ ModLoad: 00007ffe`e9100000 00007ffe`e9165000 C:\Program Files\dotnet\host\fxr\2.2.2\hostfxr.dll
+ ModLoad: 00007ffe`e7ba0000 00007ffe`e7c32000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.6\hostpolicy.dll
+ ModLoad: 00007ffe`abb60000 00007ffe`ac125000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.6\coreclr.dll
+ ntdll!ZwMapViewOfSection+0x14:
+ 00007fff`16e2fb74 c3 ret
+ 0:000> .chain
+ Extension DLL search Path:
+ C:\Program Files\Debugging Tools for Windows (x64);...
+ Extension DLL chain:
+ sos: image 1.0.1-dev.19106.2+58b97f128be8f866a08aba9fd5c77571ae8e3f6a, API 2.0.0, built Wed Feb 6 13:06:38 2019
+ [path: C:\Users\mikem\AppData\Local\DBG\ExtRepository\EG\cache2\Packages\SOS\1.0.1.0\x64\sos.dll]
+ dbghelp: image 10.0.18317.1001, API 10.0.6,
+ [path: C:\Program Files\Debugging Tools for Windows (x64)\dbghelp.dll]
+ ...
+ ntsdexts: image 10.0.18317.1001, API 1.0.0,
+ [path: C:\Program Files\Debugging Tools for Windows (x64)\WINXP\ntsdexts.dll]
+ 0:000> !soshelp
+ -------------------------------------------------------------------------------
+ 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.
+
+ Object Inspection Examining code and stacks
+ ----------------------------- -----------------------------
+ DumpObj (do) Threads (clrthreads)
+ DumpArray (da) ThreadState
+ DumpAsync IP2MD
+ DumpDelegate U
+ DumpStackObjects (dso) DumpStack
+ DumpHeap EEStack
+ ...
+
+### Older versions of the Windows debugger
+
+It is recommended that you update to the newer versions of the Windows debugger, but you can still use the latest SOS with older Windows debuggers by using the dotnet-sos CLI global tool to install. It is not as convenient. You may have to ".unload" the SOS that is loaded from the "runtime" directory.
+
+ C:\Users\mikem>dotnet tool install -g dotnet-sos --version 1.0.2-preview3.19151.2 --add-source https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json
+ You can invoke the tool using the following command: dotnet-sos
+ Tool 'dotnet-sos' (version '1.0.2-preview3.19151.2') was successfully installed.
+
+Run the installer:
+
+ C:\Users\mikem>dotnet sos install
+ Installing SOS to C:\Users\mikem\.dotnet\sos from C:\Users\mikem\.dotnet\tools\.store\dotnet-sos\1.0.2-preview3.19151.2\dotnet-sos\1.0.2-preview3.19151.2\tools\netcoreapp2.1\any\win-x64
+ Creating installation directory...
+ Copying files...
+ Execute '.load C:\Users\mikem\.dotnet\sos\sos.dll' to load SOS in your Windows debugger.
+ SOS install succeeded
+
+SOS will need to be loaded manually with the above ".load" command:
+
+
+ C:\Users\mikem>"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" dotnet SymbolTestApp2.dll
+
+ Microsoft (R) Windows Debugger Version 10.0.17134.12 AMD64
+ Copyright (c) Microsoft Corporation. All rights reserved.
+
+ CommandLine: dotnet SymbolTestApp2.dll
+ Symbol search path is: srv*
+ Executable search path is:
+ ModLoad: 00007ff7`f7450000 00007ff7`f7477000 dotnet.exe
+ ModLoad: 00007fff`16d90000 00007fff`16f7d000 ntdll.dll
+ ModLoad: 00007fff`145e0000 00007fff`14693000 C:\WINDOWS\System32\KERNEL32.DLL
+ ModLoad: 00007fff`13c30000 00007fff`13ec3000 C:\WINDOWS\System32\KERNELBASE.dll
+ ModLoad: 00007fff`13a70000 00007fff`13b6c000 C:\WINDOWS\System32\ucrtbase.dll
+ (92cd8.92eb4): Break instruction exception - code 80000003 (first chance)
+ ntdll!LdrpDoDebuggerBreak+0x30:
+ 00007fff`16e62cbc cc int 3
+ 0:000> .load C:\Users\mikem\.dotnet\sos\sos.dll
+ 0:000> .chain
+ Extension DLL search Path:
+ C:\Program Files\Debugging Tools for Windows (x64);...
+ Extension DLL chain:
+ C:\Users\mikem\.dotnet\sos\sos.dll: image 1.0.2-dev.19151.2+26ec7875d312cf57db83926db0d9340e297e2a4c, API 2.0.0, built Mon Feb 25 17:27:33 2019
+ [path: C:\Users\mikem\.dotnet\sos\sos.dll]
+ dbghelp: image 10.0.18317.1001, API 10.0.6,
+ [path: C:\Program Files\Debugging Tools for Windows (x64)\dbghelp.dll]
+ ...
+ ntsdexts: image 10.0.18317.1001, API 1.0.0,
+ [path: C:\Program Files\Debugging Tools for Windows (x64)\WINXP\ntsdexts.dll]
\ No newline at end of file
os = "linux";
}
if (os == null) {
- throw new PlatformNotSupportedException($"{RuntimeInformation.OSDescription} not supported");
+ throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
}
string architecture = RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant();
string rid = os + "-" + architecture;
if (LLDBInitFile != null) {
Configure();
}
+ else {
+ WriteLine($"Execute '.load {InstallLocation}\\sos.dll' to load SOS in your Windows debugger.");
+ }
// If we get here without an exception, success!
installSuccess = true;
result = proc_pidpath(pid, pBuffer, (uint)(PROC_PIDPATHINFO_MAXSIZE * sizeof(byte)));
if (result <= 0)
{
- throw new ArgumentException("Could not find procpath using libproc.");
+ throw new InvalidOperationException("Could not find procpath using libproc.");
}
// OS X uses UTF-8. The conversion may not strip off all trailing \0s so remove them here
var candidateName = Path.GetFileNameWithoutExtension(path);
return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
}
- catch (ArgumentException)
+ catch (InvalidOperationException)
{
return null; // The pinvoke above may fail - return null in that case to handle error gracefully.
}
/// </summary>
public class AnalyzeContext: ISOSHostContext
{
- readonly IConsole _console;
- ClrRuntime _runtime;
+ private readonly IConsole _console;
+ private ClrRuntime _runtime;
+ private SOSHost _sosHost;
public AnalyzeContext(IConsole console, DataTarget target, Action exit)
{
{
if (_runtime == null)
{
- if (Target.ClrVersions.Count != 1)
- {
+ if (Target.ClrVersions.Count != 1) {
throw new InvalidOperationException("More or less than 1 CLR version is present");
}
_runtime = Target.ClrVersions[0].CreateRuntime();
}
}
+ /// <summary>
+ /// Returns the SOS host instance
+ /// </summary>
+ public SOSHost SOSHost
+ {
+ get
+ {
+ if (_sosHost == null) {
+ _sosHost = new SOSHost(Target.DataReader, this);
+ }
+ return _sosHost;
+ }
+ }
+
/// <summary>
/// Delegate to invoke to exit repl
/// </summary>
using Microsoft.Diagnostic.Repl;
using Microsoft.Diagnostics.Runtime;
+using System;
using System.CommandLine;
using System.IO;
using System.Linq;
{
_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);
+ try
+ {
+ 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 {
+ throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
}
- // Run the commands from the dotnet-dump command line
- if (command != null)
+ using (target)
{
- foreach (string cmd in command)
+ _consoleProvider.Out.WriteLine("Ready to process analysis commands. Type 'help' to list available commands or 'help [command]' to get detailed help on a command.");
+ _consoleProvider.Out.WriteLine("Type 'quit' or 'exit' to exit the session.");
+
+ // 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)) {
+ analyzeContext.SOSHost.ExecuteCommand("SetSymbolServer", "-ms");
+ }
+
+ // Run the commands from the dotnet-dump command line
+ if (command != null)
{
- await _commandProcessor.Parse(cmd, _consoleProvider);
+ 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);
- });
+ // Start interactive command line processing
+ await _consoleProvider.Start(async (string commandLine, CancellationToken cancellation) => {
+ analyzeContext.CancellationToken = cancellation;
+ await _commandProcessor.Parse(commandLine, _consoleProvider);
+ });
+ }
+ }
+ catch (Exception ex) when
+ (ex is ClrDiagnosticsException ||
+ ex is FileNotFoundException ||
+ ex is DirectoryNotFoundException ||
+ ex is UnauthorizedAccessException ||
+ ex is PlatformNotSupportedException ||
+ ex is InvalidDataException ||
+ ex is InvalidOperationException ||
+ ex is NotSupportedException)
+ {
+ _consoleProvider.Error.WriteLine($"{ex.Message}");
+ return 1;
}
return 0;
namespace Microsoft.Diagnostic.Tools.Dump
{
[Command(Name = "exit", Help = "Exit interactive mode.")]
+ [Command(Name = "quit")]
public class ExitCommand : CommandBase
{
public AnalyzeContext AnalyzeContext { 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);
+ AnalyzeContext.SOSHost.ExecuteCommand(AliasExpansion, arguments);
}
catch (Exception ex) when (ex is FileNotFoundException || ex is EntryPointNotFoundException || ex is InvalidOperationException) {
Console.Error.WriteLine(ex.Message);
{
private static class Linux
{
- internal static async Task CollectDumpAsync(Process process, string fileName)
+ internal static async Task CollectDumpAsync(Process process, string fileName, DumpType type)
{
// We don't work on WSL :(
string ostype = await File.ReadAllTextAsync("/proc/sys/kernel/osrelease");
}
// Create the dump
- int exitCode = await CreateDumpAsync(createDumpPath, fileName, process.Id);
+ int exitCode = await CreateDumpAsync(createDumpPath, fileName, process.Id, type);
if (exitCode != 0)
{
- throw new Exception($"createdump exited with non-zero exit code: {exitCode}");
+ throw new InvalidOperationException($"createdump exited with non-zero exit code: {exitCode}");
}
}
- private static Task<int> CreateDumpAsync(string exePath, string fileName, int processId)
+ private static Task<int> CreateDumpAsync(string exePath, string fileName, int processId, DumpType type)
{
+ string dumpType = type == DumpType.Mini ? "--normal" : "--withheap";
var tcs = new TaskCompletionSource<int>();
var createdump = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = exePath,
- Arguments = $"--diag -f {fileName} {processId}",
- //RedirectStandardError = true,
- //RedirectStandardOutput = true,
- //RedirectStandardInput = true,
+ Arguments = $"--name {fileName} {dumpType} {processId}",
},
EnableRaisingEvents = true,
};
{
private static class Windows
{
- internal static Task CollectDumpAsync(Process process, string outputFile)
+ internal static Task CollectDumpAsync(Process process, string outputFile, DumpType type)
{
// We can't do this "asynchronously" so just Task.Run it. It shouldn't be "long-running" so this is fairly safe.
return Task.Run(() =>
// Open the file for writing
using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
- // Dump the process!
var exceptionInfo = new NativeMethods.MINIDUMP_EXCEPTION_INFORMATION();
- if (!NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemory, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero))
+ var dumpType = type == DumpType.Mini ? NativeMethods.MINIDUMP_TYPE.MiniDumpWithThreadInfo :
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithDataSegs |
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithPrivateReadWriteMemory |
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithHandleData |
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithUnloadedModules |
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemoryInfo |
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithThreadInfo |
+ NativeMethods.MINIDUMP_TYPE.MiniDumpWithTokenInformation;
+
+ // Dump the process!
+ if (!NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, dumpType, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero))
{
- var err = Marshal.GetHRForLastWin32Error();
+ int err = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(err);
}
}
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
+using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostic.Tools.Dump
{
public partial class Dumper
{
+ /// <summary>
+ /// The dump type determines the kinds of information that are collected from the process.
+ /// </summary>
+ public enum DumpType
+ {
+ Heap, // A large and relatively comprehensive dump containing module lists, thread lists, all
+ // stacks, exception information, handle information, and all memory except for mapped images.
+ Mini // A small dump containing module lists, thread lists, exception information and all stacks.
+ }
+
public Dumper()
{
}
- public async Task<int> Collect(IConsole console, int processId, string outputDirectory)
+ public async Task<int> Collect(IConsole console, int processId, string output, DumpType type)
{
if (processId == 0) {
console.Error.WriteLine("ProcessId is required.");
return 1;
}
- // System.CommandLine has a bug in the default value handling
- if (outputDirectory == null) {
- outputDirectory = Directory.GetCurrentDirectory();
- }
-
- // Get the process
- Process process = null;
try
{
- process = Process.GetProcessById(processId);
- }
- catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException)
- {
- console.Error.WriteLine($"Invalid process id: {processId}");
- return 1;
- }
+ // Get the process
+ Process process = Process.GetProcessById(processId);
- // Generate the file name
- string fileName = Path.Combine(outputDirectory, $"{process.ProcessName}-{process.Id}-{DateTime.Now:yyyyMMdd-HHmmss-fff}.dmp");
+ if (output == null)
+ {
+ // Build timestamp based file path
+ string timestamp = $"{DateTime.Now:yyyyMMdd_HHmmss}";
+ output = Path.Combine(Directory.GetCurrentDirectory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"dump_{timestamp}.dmp" : $"core_{timestamp}");
+ }
- console.Out.WriteLine($"Collecting memory dump for {process.ProcessName} (ID: {process.Id}) ...");
-
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
- await Windows.CollectDumpAsync(process, fileName);
- }
- else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
- await Linux.CollectDumpAsync(process, fileName);
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // Matches createdump's output on Linux
+ string dumpType = type == DumpType.Mini ? "minidump" : "minidump with heap";
+ console.Out.WriteLine($"Writing {dumpType} to {output}");
+
+ await Windows.CollectDumpAsync(process, output, type);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ await Linux.CollectDumpAsync(process, output, type);
+ }
+ else {
+ throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}");
+ }
}
- else {
- console.Error.WriteLine($"Unsupported operating system {RuntimeInformation.OSDescription}");
+ catch (Exception ex) when
+ (ex is FileNotFoundException ||
+ ex is DirectoryNotFoundException ||
+ ex is UnauthorizedAccessException ||
+ ex is PlatformNotSupportedException ||
+ ex is InvalidDataException ||
+ ex is InvalidOperationException ||
+ ex is NotSupportedException)
+ {
+ console.Error.WriteLine($"{ex.Message}");
return 1;
}
- console.Out.WriteLine($"Dump saved to {fileName}");
+ console.Out.WriteLine($"Complete");
return 0;
}
}
private static Command CollectCommand() =>
new Command(
"collect",
- "Captures memory dumps of .NET processes.",
- new Option[] { ProcessIdOption(), OutputOption() },
- handler: CommandHandler.Create<IConsole, int, string>(new Dumper().Collect));
+ "Capture dumps from a process",
+ new Option[] { ProcessIdOption(), OutputOption(), TypeOption() },
+ handler: CommandHandler.Create<IConsole, int, string, Dumper.DumpType>(new Dumper().Collect));
private static Option ProcessIdOption() =>
new Option(
new[] { "-p", "--process-id" },
- "The ID of the process to collect a memory dump.",
- new Argument<int> { Name = "processId" });
+ "The process to collect a memory dump from.",
+ new Argument<int> { Name = "pid" });
+
private static Option OutputOption() =>
new Option(
- new[] { "-o", "--output" },
- "The directory to write the dump. Defaults to the current working directory.",
- new Argument<string>(Directory.GetCurrentDirectory()) { Name = "directory" });
+ new[] { "-o", "--output" },
+ @"The path where collected dumps should be written. Defaults to '.\dump_YYYYMMDD_HHMMSS.dmp' on Windows and
+'./core_YYYYMMDD_HHMMSS' on Linux where YYYYMMDD is Year/Month/Day and HHMMSS is Hour/Minute/Second. Otherwise, it is the full
+path and file name of the dump.",
+ new Argument<string>() { Name = "output_dump_path" });
+
+ private static Option TypeOption() =>
+ new Option(
+ "--type",
+ @"The dump type determines the kinds of information that are collected from the process. There are two types:
+
+heap - A large and relatively comprehensive dump containing module lists, thread lists, all stacks,
+ exception information, handle information, and all memory except for mapped images.
+mini - A small dump containing module lists, thread lists, exception information and all stacks.
+
+If not specified 'heap' is the default.",
+ new Argument<Dumper.DumpType>(Dumper.DumpType.Heap) { Name = "dump_type" });
private static Command AnalyzeCommand() =>
new Command(
"analyze",
- "Start interactive dump analyze.",
+ "Starts an interactive shell with debugging commands to explore a dump",
new Option[] { RunCommand() }, argument: DumpPath(),
handler: CommandHandler.Create<FileInfo, string[]>(new Analyzer().Analyze));