From: Lee Culver Date: Thu, 30 Mar 2023 17:50:50 +0000 (-0700) Subject: Improve !maddress (#3792) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055542~43^2~1^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e0a90b79b374afeab3e9858cdb7afc86bfa7d035;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Improve !maddress (#3792) - 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 --- diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs index dc12f577a..66de79e8e 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/FindPointersInCommand.cs @@ -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(); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs index dbfdf5be4..1016dd685 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCToNativeCommand.cs @@ -76,7 +76,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands return; } - IEnumerable rangeEnum = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory: false, tagReserveMemoryHeuristically: false); + IEnumerable 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); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs index 0cf4185be..670719f19 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/MAddressCommand.cs @@ -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 memoryRanges = AddressHelper.EnumerateAddressSpace(tagClrMemoryRanges: true, includeReserveMemory, tagReserveMemoryHeuristically); - if (!includeReserveMemory) + IEnumerable 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 diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs index c0b163074..9d6f5f02e 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs @@ -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. + /// 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. /// If !address fails we will throw InvalidOperationException. This is usually /// because symbols for ntdll couldn't be found. /// An enumerable of memory ranges. - internal IEnumerable EnumerateAddressSpace(bool tagClrMemoryRanges, bool includeReserveMemory, bool tagReserveMemoryHeuristically) + internal IEnumerable 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(); + RootCacheService rootCache = runtime.Services.GetService(); 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 /// /// Enumerates pointers to various CLR heaps in memory. /// - 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; + } } }