Add a way to filter !eeheap (#3750)
authorLee Culver <leculver@microsoft.com>
Wed, 15 Mar 2023 00:49:46 +0000 (17:49 -0700)
committerGitHub <noreply@github.com>
Wed, 15 Mar 2023 00:49:46 +0000 (17:49 -0700)
* Add a way to filter !eeheap

- !eeheap now only operates on the currently selected runtime (again)
- Maoni requested a feature where we can give filters to !eeheap, as its output can be too long and detailed on larger dumps
- Added the same -segment, -heap, and [memory, range] arguments to !eeheap to filter the output down to the requested ranges, segments, or heaps.
- Loader/Modules heaps will also be filtered by "[memory, range]" (not not -heap and -segment).
- Changed "Total" to "Partial" whenever we filter the heap.  e.g. 'Total bytes consumed by CLR' -> 'Partial bytes consumed by CLR'.

* Add DML to !eeheap -gc

* Fix format mistake

src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/HeapWithFilters.cs
src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs
src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs

index 4c9a695da94d40fe11ff70866f5b0ace98ff2959..90afe5dc430daeb2edd8ca2cb2cbd54738612fe7 100644 (file)
@@ -8,14 +8,22 @@ using System.Linq;
 using System.Text;
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Runtime;
+using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
 {
-    [Command(Name = "eeheap", Help = "Displays information about native memory that CLR has allocated.")]
+    [Command(Name = CommandName, Help = "Displays information about native memory that CLR has allocated.")]
     public class EEHeapCommand : CommandBase
     {
+        private const string CommandName = "eeheap";
+
+        private HeapWithFilters HeapWithFilters { get; set; }
+
+        // Don't use the word "Total" if we have filtered out entries
+        private string TotalString => HeapWithFilters.HasFilters ? "Partial" : "Total";
+
         [ServiceImport]
-        public IRuntimeService RuntimeService { get; set; }
+        public ClrRuntime Runtime { get; set; }
 
         [ServiceImport]
         public IMemoryService MemoryService { get; set; }
@@ -26,32 +34,44 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "-loader", Help = "Only display the Loader.")]
         public bool ShowLoader { get; set; }
 
+        [Option(Name = "-heap")]
+        public int GCHeap { get; set; } = -1;
+
+        [Option(Name = "-segment")]
+        public string Segment { get; set; }
+
+        [Argument(Help = "Optional memory ranges in the form of: [Start [End]]")]
+        public string[] MemoryRange { get; set; }
+
         public override void Invoke()
         {
-            IRuntime[] runtimes = RuntimeService.EnumerateRuntimes().ToArray();
+            HeapWithFilters = new(Runtime.Heap)
+            {
+                // The user may want to filter loader regions by address
+                ThrowIfNoMatchingGCRegions = false
+            };
 
-            ulong totalBytes = 0;
-            StringBuilder stringBuilder = null;
-            foreach (IRuntime iRuntime in runtimes)
+            if (GCHeap >= 0)
             {
-                if (runtimes.Length > 1)
-                {
-                    WriteDivider($"{iRuntime.RuntimeType} {iRuntime.RuntimeModule?.GetVersionData()}");
-                }
+                HeapWithFilters.GCHeap = GCHeap;
+            }
 
-                ClrRuntime clrRuntime = iRuntime.Services.GetService<ClrRuntime>();
-                totalBytes += PrintOneRuntime(ref stringBuilder, clrRuntime);
+            if (!string.IsNullOrWhiteSpace(Segment))
+            {
+                HeapWithFilters.FilterBySegmentHex(Segment);
             }
 
-            // Only print the total bytes if we walked everything.
-            if (runtimes.Length > 1 && !ShowGC && !ShowLoader)
+            if (MemoryRange is not null)
             {
-                WriteLine($"Total bytes consumed by all CLRs: {FormatMemorySize(totalBytes, "0")}");
+                HeapWithFilters.FilterByStringMemoryRange(MemoryRange, CommandName);
             }
+
+            PrintOneRuntime(Runtime);
         }
 
-        private ulong PrintOneRuntime(ref StringBuilder stringBuilder, ClrRuntime clrRuntime)
+        private ulong PrintOneRuntime(ClrRuntime clrRuntime)
         {
+            StringBuilder stringBuilder = null;
             TableOutput output = new(Console, (21, "x12"), (0, "x12"))
             {
                 AlignLeft = true
@@ -78,7 +98,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             if (!ShowGC && !ShowLoader)
             {
                 WriteLine();
-                WriteLine($"Total bytes consumed by CLR: {FormatMemorySize(totalSize, "0")}");
+                WriteLine($"{TotalString} bytes consumed by CLR: {FormatMemorySize(totalSize, "0")}");
                 WriteLine();
             }
 
@@ -129,13 +149,14 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 }
             }
 
-            IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> heapsByKind = from heap in appDomain.EnumerateLoaderAllocatorHeaps()
-                                                                                           where loaderAllocatorsSeen.Add(heap.Address)
-                                                                                           group heap by heap.Kind into g
-                                                                                           orderby GetSortOrder(g.Key)
-                                                                                           select g;
+            IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> filteredHeapsByKind = from heap in appDomain.EnumerateLoaderAllocatorHeaps()
+                                                                                                   where IsIncludedInFilter(heap)
+                                                                                                   where loaderAllocatorsSeen.Add(heap.Address)
+                                                                                                   group heap by heap.Kind into g
+                                                                                                   orderby GetSortOrder(g.Key)
+                                                                                                   select g;
 
-            return PrintAppDomainHeapsByKind(output, heapsByKind);
+            return PrintAppDomainHeapsByKind(output, filteredHeapsByKind);
         }
 
         private static int GetSortOrder(NativeHeapKind key)
@@ -161,14 +182,14 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             };
         }
 
-        private ulong PrintAppDomainHeapsByKind(TableOutput output, IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> heapsByKind)
+        private ulong PrintAppDomainHeapsByKind(TableOutput output, IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> filteredHeapsByKind)
         {
             // Just build and print the table.
             ulong totalSize = 0;
             ulong totalWasted = 0;
             StringBuilder text = new(512);
 
-            foreach (IGrouping<NativeHeapKind, ClrNativeHeapInfo> item in heapsByKind)
+            foreach (IGrouping<NativeHeapKind, ClrNativeHeapInfo> item in filteredHeapsByKind)
             {
                 text.Clear();
                 NativeHeapKind kind = item.Key;
@@ -204,7 +225,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             {
                 WriteSizeAndWasted(text, totalSize, totalWasted);
                 text.Append('.');
-                output.WriteRow("Total size:", text);
+                output.WriteRow($"{TotalString} size:", text);
             }
             else
             {
@@ -218,13 +239,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         private ulong PrintCodeHeaps(TableOutput output, ClrRuntime clrRuntime)
         {
             ulong totalSize = 0;
-
             StringBuilder text = new(512);
             foreach (ClrJitManager jitManager in clrRuntime.EnumerateJitManagers())
             {
                 output.WriteRow("JIT Manager:", jitManager.Address);
 
-                IEnumerable<ClrNativeHeapInfo> heaps = jitManager.EnumerateNativeHeaps().OrderBy(r => r.Kind).ThenBy(r => r.Address);
+                IEnumerable<ClrNativeHeapInfo> heaps = jitManager.EnumerateNativeHeaps().Where(IsIncludedInFilter).OrderBy(r => r.Kind).ThenBy(r => r.Address);
 
                 ulong jitMgrSize = 0, jitMgrWasted = 0;
                 foreach (ClrNativeHeapInfo heap in heaps)
@@ -246,7 +266,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 WriteSizeAndWasted(text, jitMgrSize, jitMgrWasted);
                 text.Append('.');
 
-                output.WriteRow("Total size:", text);
+                output.WriteRow($"{TotalString} size:", text);
                 WriteDivider();
 
                 totalSize += jitMgrSize;
@@ -255,6 +275,29 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             return totalSize;
         }
 
+        private bool IsIncludedInFilter(ClrNativeHeapInfo info)
+        {
+            // ClrNativeHeapInfo is only filtered by memory range (not heap or segment).
+            if (HeapWithFilters.MemoryRange is not MemoryRange filterRange)
+            {
+                // no filter, so include everything
+                return true;
+            }
+
+            if (filterRange.Contains(info.Address))
+            {
+                return true;
+            }
+
+            if (info.Size is ulong size && size > 0)
+            {
+                // Check for the last valid address in the range
+                return filterRange.Contains(info.Address + size - 1);
+            }
+
+            return false;
+        }
+
         private (ulong Size, ulong Wasted) CalculateSizeAndWasted(StringBuilder sb, ClrNativeHeapInfo heap)
         {
             sb.Append(heap.Address.ToString("x12"));
@@ -324,7 +367,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 ulong moduleSize = 0, moduleWasted = 0;
 
                 text.Clear();
-                foreach (ClrNativeHeapInfo info in module.EnumerateThunkHeap())
+                foreach (ClrNativeHeapInfo info in module.EnumerateThunkHeap().Where(IsIncludedInFilter))
                 {
                     if (text.Length > 0)
                     {
@@ -348,7 +391,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
             text.Clear();
             WriteSizeAndWasted(text, totalSize, totalWasted);
-            output.WriteRow("Total size:", text);
+            output.WriteRow($"{TotalString} size:", text);
 
             return totalSize;
         }
@@ -408,18 +451,42 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             Console.WriteLine($"Number of GC Heaps: {heap.SubHeaps.Length}");
             WriteDivider();
 
-            foreach (ClrSubHeap gc_heap in heap.SubHeaps)
+            foreach (ClrSubHeap gc_heap in HeapWithFilters.EnumerateFilteredSubHeaps())
             {
                 if (heap.IsServer)
                 {
-                    Console.WriteLine($"Heap {gc_heap.Index} ({gc_heap.Address:x16})");
+                    Console.Write("Heap ");
+                    Console.WriteDmlExec(gc_heap.Index.ToString(), $"!dumpheap -heap {gc_heap.Index}");
+                    Console.WriteLine($" ({gc_heap.Address:x16})");
                 }
 
                 if (!gc_heap.HasRegions)
                 {
                     for (int i = 0; i <= 2 && i < gc_heap.GenerationTable.Length; i++)
                     {
-                        Console.WriteLine($"generation {i} starts at {gc_heap.GenerationTable[i].AllocationStart:x}");
+                        ClrSegment seg = heap.GetSegmentByAddress(gc_heap.GenerationTable[i].AllocationStart);
+                        MemoryRange range = default;
+                        if (seg is not null)
+                        {
+                            range = i switch
+                            {
+                                0 => seg.Generation0,
+                                1 => seg.Generation1,
+                                2 => seg.Generation2,
+                                _ => default
+                            };
+                        }
+
+                        if (range.Length > 0)
+                        {
+                            Console.Write($"generation {i} starts at ");
+                            Console.WriteDmlExec(gc_heap.GenerationTable[i].AllocationStart.ToString("x"), $"!dumpheap {range.Start:x} {range.End:x}");
+                            Console.WriteLine();
+                        }
+                        else
+                        {
+                            Console.WriteLine($"generation {i} starts at {gc_heap.GenerationTable[i].AllocationStart:x}");
+                        }
                     }
 
                     Console.Write("ephemeral segment allocation context: ");
@@ -438,7 +505,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 WriteSegmentHeader(gcOutput);
 
                 bool[] needToPrintGen = new bool[] { gc_heap.HasRegions, gc_heap.HasRegions, gc_heap.HasRegions };
-                IEnumerable<ClrSegment> ephemeralSegments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Ephemeral || (seg.Kind >= GCSegmentKind.Generation0 && seg.Kind <= GCSegmentKind.Generation2));
+                IEnumerable<ClrSegment> ephemeralSegments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Ephemeral || (seg.Kind >= GCSegmentKind.Generation0 && seg.Kind <= GCSegmentKind.Generation2));
                 IEnumerable<ClrSegment> segments = ephemeralSegments.OrderBy(seg => seg.Kind).ThenBy(seg => seg.Start);
                 foreach (ClrSegment segment in segments)
                 {
@@ -453,7 +520,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 }
 
                 // print frozen object heap
-                segments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Frozen).OrderBy(seg => seg.Start);
+                segments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Frozen).OrderBy(seg => seg.Start);
                 if (segments.Any())
                 {
                     Console.WriteLine("Frozen object heap");
@@ -475,7 +542,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                     Console.WriteLine($"Large object heap starts at {gc_heap.GenerationTable[3].AllocationStart:x}");
                 }
 
-                segments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Large).OrderBy(seg => seg.Start);
+                segments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Large).OrderBy(seg => seg.Start);
                 WriteSegmentHeader(gcOutput);
 
                 foreach (ClrSegment segment in segments)
@@ -484,7 +551,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 }
 
                 // print pinned object heap
-                segments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Pinned).OrderBy(seg => seg.Start);
+                segments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Pinned).OrderBy(seg => seg.Start);
                 if (segments.Any())
                 {
                     if (gc_heap.HasRegions || gc_heap.GenerationTable.Length <= 3)
@@ -504,17 +571,26 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                     }
                 }
 
-                Console.WriteLine($"Total Allocated Size:              Size: {FormatMemorySize((ulong)gc_heap.Segments.Sum(r => (long)r.ObjectRange.Length))} bytes.");
-                Console.WriteLine($"Total Committed Size:              Size: {FormatMemorySize((ulong)gc_heap.Segments.Sum(r => (long)r.CommittedMemory.Length))} bytes.");
+                if (HeapWithFilters.HasFilters)
+                {
+                    Console.WriteLine($"{TotalString} Allocated Size:              Size: {FormatMemorySize((ulong)HeapWithFilters.EnumerateFilteredSegments(gc_heap).Sum(r => (long)r.ObjectRange.Length))} bytes.");
+                    Console.WriteLine($"{TotalString} Committed Size:              Size: {FormatMemorySize((ulong)HeapWithFilters.EnumerateFilteredSegments(gc_heap).Sum(r => (long)r.CommittedMemory.Length))} bytes.");
+                }
 
                 Console.WriteLine("------------------------------");
             }
 
-            ulong totalAllocated = (ulong)heap.SubHeaps.SelectMany(gc_heap => gc_heap.Segments).Sum(r => (long)r.ObjectRange.Length);
-            ulong totalCommitted = (ulong)heap.SubHeaps.SelectMany(gc_heap => gc_heap.Segments).Sum(r => (long)r.CommittedMemory.Length);
+            string prefix = "";
+            if (HeapWithFilters.HasFilters)
+            {
+                prefix = "Partial ";
+            }
+
+            ulong totalAllocated = (ulong)HeapWithFilters.EnumerateFilteredSegments().Sum(r => (long)r.ObjectRange.Length);
+            ulong totalCommitted = (ulong)HeapWithFilters.EnumerateFilteredSegments().Sum(r => (long)r.CommittedMemory.Length);
 
-            Console.WriteLine($"GC Allocated Heap Size:    Size: {FormatMemorySize(totalAllocated)} bytes.");
-            Console.WriteLine($"GC Committed Heap Size:    Size: {FormatMemorySize(totalCommitted)} bytes.");
+            Console.WriteLine($"{prefix}GC Allocated Heap Size:    Size: {FormatMemorySize(totalAllocated)} bytes.");
+            Console.WriteLine($"{prefix}GC Committed Heap Size:    Size: {FormatMemorySize(totalCommitted)} bytes.");
 
             return totalCommitted;
         }
@@ -526,7 +602,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
         private static void WriteSegment(TableOutput gcOutput, ClrSegment segment)
         {
-            gcOutput.WriteRow(segment.Address,
+            gcOutput.WriteRow(new DmlDumpHeapSegment(segment),
                 segment.ObjectRange.Start, segment.ObjectRange.End, segment.CommittedMemory.End,
                 FormatMemorySize(segment.ObjectRange.Length), FormatMemorySize(segment.CommittedMemory.Length));
         }
@@ -542,30 +618,5 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         }
 
         private void WriteDivider(char c = '-', int width = 40) => WriteLine(new string(c, width));
-
-        private void WriteDivider(string header, int width = 120)
-        {
-            int lhs = (width - header.Length - 2) / 2;
-            if (lhs < 0)
-            {
-                WriteLine(header);
-                return;
-            }
-
-            int rhs = lhs;
-            if ((header.Length % 2) == 1)
-            {
-                rhs++;
-            }
-
-            StringBuilder sb = new(width + 1);
-            sb.Append('-', lhs);
-            sb.Append(' ');
-            sb.Append(header);
-            sb.Append(' ');
-            sb.Append('-', rhs);
-
-            WriteLine(sb.ToString());
-        }
     }
 }
index fb9cc06e55d1f37ecb059875b85dea17e62a00e4..7c5604e6b153c89d678d2054ad56ccec2b4187f7 100644 (file)
@@ -48,6 +48,11 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             }
         }
 
+        /// <summary>
+        /// Whether or not to throw if there are now matching segments or subheaps.
+        /// </summary>
+        public bool ThrowIfNoMatchingGCRegions { get; set; } = true;
+
         /// <summary>
         /// The minimum size of an object to enumerate.
         /// </summary>
@@ -63,10 +68,16 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         /// </summary>
         public Func<IEnumerable<ClrSegment>, IOrderedEnumerable<ClrSegment>> SortSegments { get; set; }
 
+        /// <summary>
+        /// The order in which to enumerate subheaps.  This only applies to subheap enumeration.
+        /// </summary>
+        public Func<IEnumerable<ClrSubHeap>, IOrderedEnumerable<ClrSubHeap>> SortSubHeaps { get; set; }
+
         public HeapWithFilters(ClrHeap heap)
         {
             _heap = heap;
             SortSegments = (seg) => seg.OrderBy(s => s.SubHeap.Index).ThenBy(s => s.Address);
+            SortSubHeaps = (heap) => heap.OrderBy(heap => heap.Index);
         }
 
         public void FilterBySegmentHex(string segmentStr)
@@ -76,7 +87,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 throw new ArgumentException($"Invalid segment address: {segmentStr}");
             }
 
-            if (!_heap.Segments.Any(seg => seg.Address == segment || seg.CommittedMemory.Contains(segment)))
+            if (ThrowIfNoMatchingGCRegions && !_heap.Segments.Any(seg => seg.Address == segment || seg.CommittedMemory.Contains(segment)))
             {
                 throw new ArgumentException($"No segments match address: {segment:x}");
             }
@@ -84,6 +95,27 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             Segment = segment;
         }
 
+        public void FilterByStringMemoryRange(string[] memoryRange, string commandName)
+        {
+            if (memoryRange.Length > 0)
+            {
+                if (memoryRange.Length > 2)
+                {
+                    string badArgument = memoryRange.FirstOrDefault(f => f.StartsWith("-") || f.StartsWith("/"));
+                    if (badArgument != null)
+                    {
+                        throw new ArgumentException($"Unknown argument: {badArgument}");
+                    }
+
+                    throw new ArgumentException($"Too many arguments to !{commandName}");
+                }
+
+                string start = memoryRange[0];
+                string end = memoryRange.Length > 1 ? memoryRange[1] : null;
+                FilterByHexMemoryRange(start, end);
+            }
+        }
+
         public void FilterByHexMemoryRange(string startStr, string endStr)
         {
             if (!ulong.TryParse(startStr, NumberStyles.HexNumber, null, out ulong start))
@@ -122,15 +154,43 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 MemoryRange = new(start, end);
             }
 
-            if (!_heap.Segments.Any(seg => seg.CommittedMemory.Overlaps(MemoryRange.Value)))
+            if (ThrowIfNoMatchingGCRegions && !_heap.Segments.Any(seg => seg.CommittedMemory.Overlaps(MemoryRange.Value)))
             {
                 throw new ArgumentException($"No segments or objects in range {MemoryRange.Value}");
             }
         }
 
-        public IEnumerable<ClrSegment> EnumerateFilteredSegments()
+        public IEnumerable<ClrSubHeap> EnumerateFilteredSubHeaps()
+        {
+            IEnumerable<ClrSubHeap> subheaps = _heap.SubHeaps;
+            if (GCHeap is int gcheap)
+            {
+                subheaps = subheaps.Where(heap => heap.Index == gcheap);
+            }
+
+            if (Segment is ulong segment)
+            {
+                subheaps = subheaps.Where(heap => heap.Segments.Any(seg => seg.Address == segment || seg.CommittedMemory.Contains(segment)));
+            }
+
+            if (MemoryRange is MemoryRange range)
+            {
+                subheaps = subheaps.Where(heap => heap.Segments.Any(seg => seg.CommittedMemory.Overlaps(range)));
+            }
+
+            if (SortSubHeaps is not null)
+            {
+                subheaps = SortSubHeaps(subheaps);
+            }
+
+            return subheaps;
+        }
+
+        public IEnumerable<ClrSegment> EnumerateFilteredSegments() => EnumerateFilteredSegments(null);
+
+        public IEnumerable<ClrSegment> EnumerateFilteredSegments(ClrSubHeap subheap)
         {
-            IEnumerable<ClrSegment> segments = _heap.Segments;
+            IEnumerable<ClrSegment> segments = subheap != null ? subheap.Segments : _heap.Segments;
             if (GCHeap is int gcheap)
             {
                 segments = segments.Where(seg => seg.SubHeap.Index == gcheap);
index 6c3d3662de1bd5d8be2caaf9153f5f4da7811a61..fb03d164e2680420040c46d376331b910033f9a8 100644 (file)
@@ -277,5 +277,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             {
             }
         }
+
+        public sealed class DmlDumpHeapSegment : DmlExec
+        {
+            public DmlDumpHeapSegment(ClrSegment seg)
+                : base(seg?.Address ?? 0, seg != null ? $"!dumpheap -segment {seg.Address:x}" : "")
+            {
+            }
+        }
     }
 }
index 95335a6e41e5757b209df547297dd1acb08414d8..f204d69124ff22e923d605270a8eed93864cc63e 100644 (file)
@@ -10,9 +10,11 @@ using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
 {
-    [Command(Name = "verifyheap", Help = "Searches the managed heap for memory corruption..")]
+    [Command(Name = CommandName, Help = "Searches the managed heap for memory corruption..")]
     public class VerifyHeapCommand : CommandBase
     {
+        private const string CommandName = "verifyheap";
+
         private int _totalObjects;
 
         [ServiceImport]
@@ -43,22 +45,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 filteredHeap.FilterBySegmentHex(Segment);
             }
 
-            if (MemoryRange is not null && MemoryRange.Length > 0)
+            if (MemoryRange is not null)
             {
-                if (MemoryRange.Length > 2)
-                {
-                    string badArgument = MemoryRange.FirstOrDefault(f => f.StartsWith("-") || f.StartsWith("/"));
-                    if (badArgument != null)
-                    {
-                        throw new ArgumentException($"Unknown argument: {badArgument}");
-                    }
-
-                    throw new ArgumentException("Too many arguments to !verifyheap");
-                }
-
-                string start = MemoryRange[0];
-                string end = MemoryRange.Length > 1 ? MemoryRange[1] : null;
-                filteredHeap.FilterByHexMemoryRange(start, end);
+                filteredHeap.FilterByStringMemoryRange(MemoryRange, CommandName);
             }
 
             VerifyHeap(filteredHeap.EnumerateFilteredObjects(Console.CancellationToken), verifySyncTable: filteredHeap.HasFilters);