Bring dotnet-dump collect up to spec.
authorMike McLaughlin <mikem@microsoft.com>
Sat, 23 Feb 2019 21:08:02 +0000 (13:08 -0800)
committerMike McLaughlin <mikem@microsoft.com>
Fri, 1 Mar 2019 22:20:31 +0000 (14:20 -0800)
Issue #125

Spec changes:

    Remove the --number and --interval-sec dotnet-dump collect options.

    Change the dotnet-dump collect file naming to timestamp based.

    Change the collect spew to match createdump's.

    Lowercase all the dotnet-dump analyze commands to match sos on Linux/lldb.

Add some installing sos documentation.

15 files changed:
README.md
documentation/design-docs/dotnet-tools.md
documentation/installing-sos-instructions.md [new file with mode: 0644]
documentation/installing-sos-windows-instructions.md [new file with mode: 0644]
src/SOS/SOS.Hosting/SOSHost.cs
src/SOS/SOS.InstallHelper/InstallHelper.cs
src/Tools/dotnet-collect/ConfigPathDetector.cs
src/Tools/dotnet-dump/AnalyzeContext.cs
src/Tools/dotnet-dump/Analyzer.cs
src/Tools/dotnet-dump/Commands/ExitCommand.cs
src/Tools/dotnet-dump/Commands/SOSCommand.cs
src/Tools/dotnet-dump/Dumper.Linux.cs
src/Tools/dotnet-dump/Dumper.Windows.cs
src/Tools/dotnet-dump/Dumper.cs
src/Tools/dotnet-dump/Program.cs

index 05ca6b345eec0e7f49f3b39670c9047358b0ce11..b58c2737dc262555c35b1559d36376ad6b176a82 100644 (file)
--- a/README.md
+++ b/README.md
@@ -77,6 +77,11 @@ Getting a version of lldb that works for your platform can be a problem sometime
 * [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)
index a085f49e349330bfd8173059e210648bd95f81f2..b31cbd336dbe4bca6c70cae67fe29968a6270633 100644 (file)
@@ -65,24 +65,27 @@ For analyzing CPU usage, IO, lock contention, allocation rate, etc the investiga
 
 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
@@ -91,10 +94,10 @@ Next the investigator needs to compare the heaps in these two dumps.
     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     
@@ -106,7 +109,7 @@ Next the investigator needs to compare the heaps in these two dumps.
      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
@@ -118,10 +121,9 @@ Next the investigator needs to compare the heaps in these two dumps.
 
      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
 
@@ -404,38 +406,28 @@ OPTIONS
 
 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:
@@ -447,16 +439,16 @@ COLLECT
 
 
      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
 
@@ -464,27 +456,24 @@ 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
 
@@ -492,7 +481,7 @@ GCHEAPDIFF
         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
@@ -502,7 +491,7 @@ GCHEAPDIFF
       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 ##
@@ -1054,7 +1043,8 @@ ProcDump uses CLI convention: ProcDump [options]
        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.
@@ -1122,4 +1112,4 @@ perfmon </res|report|rel|sys>
 
 ### LTTNG
 
-TODO
\ No newline at end of file
+TODO
diff --git a/documentation/installing-sos-instructions.md b/documentation/installing-sos-instructions.md
new file mode 100644 (file)
index 0000000..9a93adf
--- /dev/null
@@ -0,0 +1,95 @@
+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.
diff --git a/documentation/installing-sos-windows-instructions.md b/documentation/installing-sos-windows-instructions.md
new file mode 100644 (file)
index 0000000..9082e66
--- /dev/null
@@ -0,0 +1,93 @@
+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
index 9df525ee9024ebf2a95f4250d115cda33457e744..36896d6ec9f381eddc2866489065fccba6263d89 100644 (file)
@@ -48,7 +48,7 @@ namespace SOS
                 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;
index 7cadaebf3a98f4d86ab943130cf8fb9957a17111..737c5ac5ca601e6c83ed14ec3c837b29d005d0ed 100644 (file)
@@ -134,6 +134,9 @@ namespace SOS
                 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;
index 619fc9465622b13d9317fb039ed113cdcf45c540..050f546026df35f100befbad3d3782c92c905d41 100644 (file)
@@ -70,7 +70,7 @@ namespace Microsoft.Diagnostics.Tools.Collect
                 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
@@ -86,7 +86,7 @@ namespace Microsoft.Diagnostics.Tools.Collect
                     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.
                 }
index 52315aa84cb6925f9098ac6de0c9e541dcc2f5c6..9fff5af27838cf2865d9095bcfa8b0f5fde9f448 100644 (file)
@@ -16,8 +16,9 @@ namespace Microsoft.Diagnostic.Tools.Dump
     /// </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)
         {
@@ -40,8 +41,7 @@ namespace Microsoft.Diagnostic.Tools.Dump
             {
                 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();
@@ -50,6 +50,20 @@ namespace Microsoft.Diagnostic.Tools.Dump
             }
         }
 
+        /// <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>
index 480f57bd615f61a749d7412f9213af2a7e8971e9..69939f3b7c125cd0498b8bec24a43afe8e17c21b 100644 (file)
@@ -1,5 +1,6 @@
 using Microsoft.Diagnostic.Repl;
 using Microsoft.Diagnostics.Runtime;
+using System;
 using System.CommandLine;
 using System.IO;
 using System.Linq;
@@ -25,45 +26,62 @@ namespace Microsoft.Diagnostic.Tools.Dump
         {
             _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;
index 704173de170193f260d86e48a9d075e2fd0afc7d..99e73af78ab95fb34ef1a0967900edf36c4625b9 100644 (file)
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
 namespace Microsoft.Diagnostic.Tools.Dump
 {
     [Command(Name = "exit", Help = "Exit interactive mode.")]
+    [Command(Name = "quit")]
     public class ExitCommand : CommandBase
     {
         public AnalyzeContext AnalyzeContext { get; set; }
index fb111716a6366e20212453eb2d4bb2f160a27bcc..36e53b7c7c4e744eee94e6cccfd236072724289f 100644 (file)
@@ -47,19 +47,14 @@ namespace Microsoft.Diagnostic.Tools.Dump
 
         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);
index db3f7fd3f93b7aa2d6c728cfe249d0594e224cc6..50700e57707f247b2fbe2cf6e5e5ddd24afce1a9 100644 (file)
@@ -10,7 +10,7 @@ namespace Microsoft.Diagnostic.Tools.Dump
     {
         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");
@@ -35,25 +35,23 @@ namespace Microsoft.Diagnostic.Tools.Dump
                 }
 
                 // 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,
                 };
index a78626d82606d52e75ca2387f6c6d0017a00c529..e691acfa522364ebd7d7963e67db7d7461a40342 100644 (file)
@@ -11,7 +11,7 @@ namespace Microsoft.Diagnostic.Tools.Dump
     {
         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(() =>
@@ -19,11 +19,20 @@ namespace Microsoft.Diagnostic.Tools.Dump
                     // 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);
                         }
                     }
index cede967d13f78649978a5d8c1ccd0016f5aab8b3..3514a5aa7f9fc44c715e3c3f23e1a637acfd4eff 100644 (file)
@@ -3,57 +3,76 @@ using System.CommandLine;
 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;
         }
     }
index 8509b5c9c34cc63485636bcadbb3c895a104d66e..a5694f2b49b2c1dc4c76ae7273fd7a1d40eb0ad3 100644 (file)
@@ -22,26 +22,41 @@ namespace Microsoft.Diagnostic.Tools.Dump
         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));