Improve !maddress (#3792)
authorLee Culver <leculver@microsoft.com>
Thu, 30 Mar 2023 17:50:50 +0000 (10:50 -0700)
committerGitHub <noreply@github.com>
Thu, 30 Mar 2023 17:50:50 +0000 (10:50 -0700)
- Using single '-' instead of '--' for Options to match the rest of SOS
- Change the default behavior of maddress to print the entire list of memory, this matches !address
- Only display the HandleTable if we can efficiently do so (or if the user requests that we show HandleTable data anyway)
- Use the RootCacheService to only walk the handle table once per runtime

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

index dc12f577a4a0648e87bbe9fefa611e2b9dfe0470..66de79e8e76374528da1b199aeb73bb0391421a7 100644 (file)
@@ -63,7 +63,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
         private void PrintPointers(bool pinnedOnly, params string[] memTypes)
         {
-            DescribedRegion[] allRegions = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false).ToArray();
+            DescribedRegion[] allRegions = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false, includeHandleTableIfSlow: false).ToArray();
 
             WriteLine("Scanning for pinned objects...");
             MemoryWalkContext ctx = CreateMemoryWalkContext();
index dbfdf5be45bcf38362c65f33542320133a36a948..1016dd685a18d251110021173b1623f96984cee0 100644 (file)
@@ -76,7 +76,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 return;
             }
 
-            IEnumerable<DescribedRegion> rangeEnum = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false);
+            IEnumerable<DescribedRegion> rangeEnum = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false, includeHandleTableIfSlow: false);
             rangeEnum = rangeEnum.Where(r => memoryTypes.Any(memType => r.Name.Equals(memType, StringComparison.OrdinalIgnoreCase)));
             rangeEnum = rangeEnum.OrderBy(r => r.Start);
 
index 0cf4185bed9d3aebefc1d8f0a14be8f1d7d80fe3..670719f1935d0bce25011d2be680377fca723f7b 100644 (file)
@@ -12,18 +12,27 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     [Command(Name = "maddress", Help = "Displays a breakdown of the virtual address space.")]
     public sealed class MAddressCommand : CommandBase
     {
-        [Option(Name = "--list", Aliases = new string[] { "-l", "--all", }, Help = "Prints the full list of annotated memory regions.")]
-        public bool ListAll { get; set; }
+        private const string ImagesFlag = "-images";
+        private const string SummaryFlag = "-summary";
+        private const string ReserveFlag = "-reserve";
+        private const string ReserveHeuristicFlag = "-reserveHeuristic";
+        private const string ForceHandleTableFlag = "-forceHandleTable";
 
-        [Option(Name = "--images", Aliases = new string[] { "-i", "-image", "-img", "--img" }, Help = "Prints the table of image memory usage.")]
+        [Option(Name = SummaryFlag, Aliases = new string[] { "-stat", }, Help = "Only print summary table.")]
+        public bool Summary { get; set; }
+
+        [Option(Name = ImagesFlag, Aliases = new string[] { "-i" }, Help = "Prints a summary table of image memory usage.")]
         public bool ShowImageTable { get; set; }
 
-        [Option(Name = "--includeReserve", Help = "Include MEM_RESERVE regions in the output.")]
+        [Option(Name = ReserveFlag, Help = "Include MEM_RESERVE regions in the output.")]
         public bool IncludeReserveMemory { get; set; }
 
-        [Option(Name = "--tagReserve", Help = "Heuristically tag MEM_RESERVE regions based on adjacent memory regions.")]
+        [Option(Name = ReserveHeuristicFlag, Help = "Heuristically tag MEM_RESERVE regions based on adjacent memory regions.")]
         public bool TagReserveMemoryHeuristically { get; set; }
 
+        [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; }
+
         [ServiceImport]
         public NativeAddressHelper AddressHelper { get; set; }
 
@@ -31,16 +40,11 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         {
             if (TagReserveMemoryHeuristically && !IncludeReserveMemory)
             {
-                throw new DiagnosticsException("Cannot use --tagReserve without --includeReserve");
+                throw new DiagnosticsException($"Cannot use {ReserveHeuristicFlag} without {ReserveFlag}");
             }
 
-            PrintMemorySummary(ListAll, ShowImageTable, IncludeReserveMemory, TagReserveMemoryHeuristically);
-        }
-
-        public void PrintMemorySummary(bool printAllMemory, bool showImageTable, bool includeReserveMemory, bool tagReserveMemoryHeuristically)
-        {
-            IEnumerable<DescribedRegion> memoryRanges = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory, tagReserveMemoryHeuristically);
-            if (!includeReserveMemory)
+            IEnumerable<DescribedRegion> memoryRanges = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, IncludeReserveMemory, TagReserveMemoryHeuristically, IncludeHandleTableIfSlow);
+            if (!IncludeReserveMemory)
             {
                 memoryRanges = memoryRanges.Where(m => m.State != MemoryRegionState.MEM_RESERVE);
             }
@@ -50,12 +54,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             int nameSizeMax = ranges.Max(r => r.Name.Length);
 
             // Tag reserved memory based on what's adjacent.
-            if (tagReserveMemoryHeuristically)
+            if (TagReserveMemoryHeuristically)
             {
                 CollapseReserveRegions(ranges);
             }
 
-            if (printAllMemory)
+            if (!Summary)
             {
                 int kindSize = ranges.Max(r => r.Type.ToString().Length);
                 int stateSize = ranges.Max(r => r.State.ToString().Length);
@@ -70,13 +74,15 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 output.WriteRowWithSpacing('-', "Memory Kind", "StartAddr", "EndAddr-1", "Size", "Type", "State", "Protect", "Image");
                 foreach (DescribedRegion mem in ranges)
                 {
+                    Console.CancellationToken.ThrowIfCancellationRequested();
+
                     output.WriteRow(mem.Name, mem.Start, mem.End, mem.Size.ConvertToHumanReadable(), mem.Type, mem.State, mem.Protection, mem.Image);
                 }
 
                 output.WriteSpacer('-');
             }
 
-            if (showImageTable)
+            if (ShowImageTable)
             {
                 var imageGroups = from mem in ranges.Where(r => r.State != MemoryRegionState.MEM_RESERVE && r.Image != null)
                                   group mem by mem.Image into g
@@ -101,6 +107,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 long size = 0;
                 foreach (var item in imageGroups)
                 {
+                    Console.CancellationToken.ThrowIfCancellationRequested();
+
                     output.WriteRow(item.Image, item.Count, item.Size.ConvertToHumanReadable(), item.Size);
                     count += item.Count;
                     size += item.Size;
@@ -137,6 +145,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 long size = 0;
                 foreach (var item in grouped)
                 {
+                    Console.CancellationToken.ThrowIfCancellationRequested();
+
                     output.WriteRow(item.Name, item.Count, item.Size.ConvertToHumanReadable(), item.Size);
                     count += item.Count;
                     size += item.Size;
@@ -152,26 +162,31 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         public void HelpInvoke()
         {
             WriteLine(
-@"-------------------------------------------------------------------------------
-!maddress is a managed version of !address, which attempts to annotate all memory
+$@"-------------------------------------------------------------------------------
+maddress is a managed version of !address, which attempts to annotate all memory
 with information about CLR's heaps.
 
-usage: !maddress [--list] [--images] [--includeReserve [--tagReserve]]
+usage: !sos maddress [{SummaryFlag}] [{ImagesFlag}] [{ForceHandleTableFlag}] [{ReserveFlag} [{ReserveHeuristicFlag}]]
 
 Flags:
-    --list
-        Shows the full list of annotated memory regions and not just the statistics
-        table.
+    {SummaryFlag}
+        Show only a summary table of memory regions and not the list of every address region.
 
-    --images
+    {ImagesFlag}
         Summarizes the memory ranges consumed by images in the process.
+
+    {ForceHandleTableFlag}
+        Ensures that we will always tag HandleTable memory.
+        On older versions of CLR, we did not have an efficient way to tag HandleTable
+        memory.  As a result, we have to fully enumerate the HandleTable to find
+        which regions of memory contain 
         
-    --includeReserve
+    {ReserveFlag}
         Include reserved memory (MEM_RESERVE) in the output.  This is usually only
         useful if there is virtual address exhaustion.
 
-    --tagReserve
-        If this flag is set, then !maddress will attempt to ""blame"" reserve segments
+    {ReserveHeuristicFlag}
+        If this flag is set, then maddress will attempt to ""blame"" reserve segments
         on the region that immediately proceeded it.  For example, if a ""Heap""
         memory segment is immediately followed by a MEM_RESERVE region, we will call
         that reserve region HeapReserve.  Note that this is a heuristic and NOT
index c0b163074b64649f7892a34ebb98391505e0a111..9d6f5f02e7d57aef25c2d2f00718382b1656bb7e 100644 (file)
@@ -52,10 +52,13 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         /// a MEM_COMMIT region is next to an unrelated MEM_RESERVE region.
         ///
         /// This is a heuristic, so use it accordingly.</param>
+        /// <param name="includeHandleTableIfSlow">If we cannot efficiently enumerate the handle table, we may have to
+        /// resort to find the memory regions associated with the HandleTable.  When a lot of handles are present, this
+        /// can take a very long time.</param>
         /// <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)
+        internal IEnumerable<DescribedRegion> EnumerateAddressSpace(bool tagClrMemoryRanges, bool includeReserveMemory, bool tagReserveMemoryHeuristically, bool includeHandleTableIfSlow)
         {
             bool printedTruncatedWarning = false;
 
@@ -74,9 +77,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 foreach (IRuntime runtime in RuntimeService.EnumerateRuntimes())
                 {
                     ClrRuntime clrRuntime = runtime.Services.GetService<ClrRuntime>();
+                    RootCacheService rootCache = runtime.Services.GetService<RootCacheService>();
                     if (clrRuntime is not null)
                     {
-                        foreach ((ulong Address, ulong? Size, ClrMemoryKind Kind) mem in EnumerateClrMemoryAddresses(clrRuntime))
+                        foreach ((ulong Address, ulong? Size, ClrMemoryKind Kind) mem in EnumerateClrMemoryAddresses(clrRuntime, rootCache, includeHandleTableIfSlow))
                         {
                             DescribedRegion[] found = rangeList.Where(r => r.Start <= mem.Address && mem.Address < r.End).ToArray();
 
@@ -240,27 +244,30 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         /// <summary>
         /// Enumerates pointers to various CLR heaps in memory.
         /// </summary>
-        private static IEnumerable<(ulong Address, ulong? Size, ClrMemoryKind Kind)> EnumerateClrMemoryAddresses(ClrRuntime runtime)
+        private static IEnumerable<(ulong Address, ulong? Size, ClrMemoryKind Kind)> EnumerateClrMemoryAddresses(ClrRuntime runtime, RootCacheService rootCache, bool includeHandleTableIfSlow)
         {
             foreach (ClrNativeHeapInfo nativeHeap in runtime.EnumerateClrNativeHeaps())
             {
                 yield return (nativeHeap.Address, nativeHeap.Size, nativeHeap.Kind == NativeHeapKind.Unknown ? ClrMemoryKind.None : (ClrMemoryKind)nativeHeap.Kind);
             }
 
-            ulong prevHandle = 0;
-            ulong granularity = 0x100;
-            foreach (ClrHandle handle in runtime.EnumerateHandles())
+            if (includeHandleTableIfSlow)
             {
-                // There can be a very large number of HandleTable entries.  We don't need to enumerate every
-                // single one of them to find proper regions of memory.  Instead, we'll skip handles that are
-                // "nearby" the previous handles we enumerated, but we will ensure that we always enumerate the
-                // next handle along an allocation granularity.  We need to ensure that 'granularity' is less
-                // than the size of a handle table chunk, and is a power of 2.
-
-                if (handle.Address < prevHandle || handle.Address >= (prevHandle | (granularity - 1)))
+                ulong prevHandle = 0;
+                ulong granularity = 0x100;
+                foreach (ClrHandle handle in rootCache.GetHandleRoots())
                 {
-                    yield return (handle.Address, null, ClrMemoryKind.HandleTable);
-                    prevHandle = handle.Address;
+                    // There can be a very large number of HandleTable entries.  We don't need to enumerate every
+                    // single one of them to find proper regions of memory.  Instead, we'll skip handles that are
+                    // "nearby" the previous handles we enumerated, but we will ensure that we always enumerate the
+                    // next handle along an allocation granularity.  We need to ensure that 'granularity' is less
+                    // than the size of a handle table chunk, and is a power of 2.
+
+                    if (handle.Address < prevHandle || handle.Address >= (prevHandle | (granularity - 1)))
+                    {
+                        yield return (handle.Address, null, ClrMemoryKind.HandleTable);
+                        prevHandle = handle.Address;
+                    }
                 }
             }