Add -list to !sos maddress (#3798)
authorLee Culver <leculver@microsoft.com>
Fri, 31 Mar 2023 19:54:25 +0000 (12:54 -0700)
committerGitHub <noreply@github.com>
Fri, 31 Mar 2023 19:54:25 +0000 (19:54 +0000)
- Added a way to list all kinds of a specific memory type
- Added caching to NativeAddressHelper, as it was recalculating every run

src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs

index 670719f1935d0bce25011d2be680377fca723f7b..7e59762b94d8f34c761cfe077922009e7822daa7 100644 (file)
@@ -17,6 +17,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         private const string ReserveFlag = "-reserve";
         private const string ReserveHeuristicFlag = "-reserveHeuristic";
         private const string ForceHandleTableFlag = "-forceHandleTable";
+        private const string ListFlag = "-list";
 
         [Option(Name = SummaryFlag, Aliases = new string[] { "-stat", }, Help = "Only print summary table.")]
         public bool Summary { get; set; }
@@ -33,6 +34,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = ForceHandleTableFlag, Help = "We only tag the HandleTable if we can do so efficiently on newer runtimes.  This option ensures we always tag HandleTable memory, even if it will take a long time.")]
         public bool IncludeHandleTableIfSlow { get; set; }
 
+        [Option(Name = ListFlag, Help = "A separated list of regions to list allocations for.")]
+        public string List { get; set; }
+
         [ServiceImport]
         public NativeAddressHelper AddressHelper { get; set; }
 
@@ -59,7 +63,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 CollapseReserveRegions(ranges);
             }
 
-            if (!Summary)
+            if (!Summary && List is null)
             {
                 int kindSize = ranges.Max(r => r.Type.ToString().Length);
                 int stateSize = ranges.Max(r => r.State.ToString().Length);
@@ -120,8 +124,46 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             }
 
 
-            // Print summary table unconditionally
+            if (List is not null)
             {
+                // Print a list of the specified memory regions, ordered by size descending.
+
+                string[] requested = List.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
+                foreach (string kind in requested)
+                {
+                    if (!ranges.Any(r => r.Name.Equals(kind, StringComparison.OrdinalIgnoreCase)))
+                    {
+                        Console.WriteLineError($"No memory regions match '{kind}'.");
+                    }
+                    else
+                    {
+                        Console.WriteLine($"{kind} Memory Regions:");
+
+                        TableOutput output = new(Console, (16, "x12"), (16, "n0"), (8, ""), (12, ""), (12, ""));
+                        output.WriteRow("Base Address", "Size (bytes)", "Size", "Mem State", "Mem Type", "Mem Protect");
+
+                        ulong totalSize = 0;
+                        int count = 0;
+
+                        IEnumerable<DescribedRegion> matching = ranges.Where(r => r.Name.Equals(kind, StringComparison.OrdinalIgnoreCase)).OrderByDescending(s => s.Size);
+                        foreach (DescribedRegion region in matching)
+                        {
+                            output.WriteRow(region.Start, region.Size, region.Size.ConvertToHumanReadable(), region.State, region.Type, region.Protection);
+
+                            count++;
+                            totalSize += region.Size;
+                        }
+
+                        Console.WriteLine($"{totalSize:n0} bytes ({totalSize.ConvertToHumanReadable()}) in {count:n0} regions");
+                        Console.WriteLine();
+                    }
+                }
+            }
+
+            if (List is null || Summary)
+            {
+                // Show the summary table in almost every case, unless the user specified -list without -summary.
+
                 var grouped = from mem in ranges
                               let name = mem.Name
                               group mem by name into g
@@ -192,6 +234,10 @@ Flags:
         that reserve region HeapReserve.  Note that this is a heuristic and NOT
         intended to be completely accurate.  This can be useful to try to figure out
         what is creating large amount of MEM_RESERVE regions.
+
+    {ListFlag}
+        A separated list of region types (as maddress defines them) to print the base
+        addresses and sizes of.  This list may be separated by , or ""in quotes"".
 ");
         }
     }
index 9d6f5f02e7d57aef25c2d2f00718382b1656bb7e..8a2c03cfff9425f9cb68f09a7685fbc94bdb2287 100644 (file)
@@ -15,6 +15,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [ServiceExport(Scope = ServiceScope.Target)]
     public sealed class NativeAddressHelper
     {
+        private ((bool, bool, bool, bool) Key, DescribedRegion[] Result) _previous;
+
         [ServiceImport]
         public ITarget Target { get; set; }
 
@@ -58,8 +60,27 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         /// <exception cref="InvalidOperationException">If !address fails we will throw InvalidOperationException.  This is usually
         /// because symbols for ntdll couldn't be found.</exception>
         /// <returns>An enumerable of memory ranges.</returns>
-        internal IEnumerable<DescribedRegion> EnumerateAddressSpace(bool tagClrMemoryRanges, bool includeReserveMemory, bool tagReserveMemoryHeuristically, bool includeHandleTableIfSlow)
+        public IEnumerable<DescribedRegion> EnumerateAddressSpace(bool tagClrMemoryRanges, bool includeReserveMemory, bool tagReserveMemoryHeuristically, bool includeHandleTableIfSlow)
+        {
+            (bool, bool, bool, bool) key = (tagClrMemoryRanges, includeReserveMemory, tagReserveMemoryHeuristically, includeHandleTableIfSlow);
+
+            if (_previous.Result is not null && _previous.Key == key)
+            {
+                return _previous.Result;
+            }
+
+            DescribedRegion[] result = EnumerateAddressSpaceWorker(tagClrMemoryRanges, includeReserveMemory, tagReserveMemoryHeuristically, includeHandleTableIfSlow);
+            _previous = (key, result);
+
+            // Use AsReadOnly to ensure no modifications to the cached value
+            return Array.AsReadOnly(result);
+        }
+
+        private DescribedRegion[] EnumerateAddressSpaceWorker(bool tagClrMemoryRanges, bool includeReserveMemory, bool tagReserveMemoryHeuristically, bool includeHandleTableIfSlow)
         {
+            Console.WriteLineWarning("Enumerating and tagging the entire address space and caching the result...");
+            Console.WriteLineWarning("Subsequent runs of this command should be faster.");
+
             bool printedTruncatedWarning = false;
 
             IEnumerable<DescribedRegion> addressResult = from region in MemoryRegionService.EnumerateRegions()
@@ -90,9 +111,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
                                 if (!printedTruncatedWarning)
                                 {
-                                    Console.WriteLine($"Warning:  Could not find a memory range for {mem.Address:x} - {mem.Kind}.");
-                                    Console.WriteLine($"This crash dump may not be a full dump!");
-                                    Console.WriteLine("");
+                                    Console.WriteLineWarning($"Warning:  Could not find a memory range for {mem.Address:x} - {mem.Kind}.");
+                                    Console.WriteLineWarning($"This crash dump may not be a full dump!");
+                                    Console.WriteLineWarning("");
 
                                     printedTruncatedWarning = true;
                                 }
@@ -477,7 +498,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             HandleTable,
         }
 
-        internal sealed class DescribedRegion : IMemoryRegion
+        public sealed class DescribedRegion : IMemoryRegion
         {
             public DescribedRegion()
             {