From: Lee Culver Date: Mon, 24 Apr 2023 15:14:01 +0000 (-0700) Subject: Reimplement !dso and !fq in C# (#3837) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055542~39^2^2~79 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0dd3b75902a849e209752f6ec94af91134a297f3;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Reimplement !dso and !fq in C# (#3837) * Add initial managed !fq command * Update ClrMD version * Finish managed !fq implementation * Implement managed !dumpstackobjs * Remove !dso and !fq C++ implementations * Remove more dead code * More dead code * Remove ObjectSize helpers * Remove usages of g_snapshot * Remove GCHeap logic from SOS * Cleanup * Add RcwCleanup output * Ensure what we don't read past stack range * Change validation * Update validation * Add registers, fix output * Remove !dso heading in C++ * Fix dso regexes * Always print the statistics table * Always print dumpheap statistics * Add proper flush check * Restore previous DumpHeapService behavior Only print header when we have objects. * Update ClrMD version * Code review feedback * TESTONLY: Add !runtimes to get more diagnostics * TESTONLY: Fix previous command * TESTONLY: Write all warning/errors to normal * TESTONLY: Printf debugging * Fix DumpStackObject failures. Remove the test only code. Enable sos logging to a file --------- Co-authored-by: Mike McLaughlin --- diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6dd5b18a9..53bdf10a6 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -4,13 +4,13 @@ https://github.com/dotnet/symstore cfbac6b5f6f9a628e89dbd38c074cbd41665820e - + https://github.com/microsoft/clrmd - a85ab0d3e53eb7aef4b3a2f1d595da18342e8696 + 80b19b666752ae64301e33f819c39d4279ec0727 - + https://github.com/microsoft/clrmd - a85ab0d3e53eb7aef4b3a2f1d595da18342e8696 + 80b19b666752ae64301e33f819c39d4279ec0727 diff --git a/eng/Versions.props b/eng/Versions.props index 85f4d5074..36b9b1096 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -45,7 +45,7 @@ 5.0.0 6.0.0 - 3.0.0-beta.23214.2 + 3.0.0-beta.23219.1 16.9.0-beta1.21055.5 3.0.7 6.0.0 diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapService.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapService.cs index f9687f8f3..3fddb7358 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapService.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapService.cs @@ -42,11 +42,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands Dictionary stats = new(); TableOutput thinLockOutput = null; - TableOutput objectTable = new(Console, (12, "x12"), (12, "x12"), (12, ""), (0, "")); - if (!statsOnly && (displayKind is DisplayKind.Normal or DisplayKind.Strings)) - { - objectTable.WriteRow("Address", "MT", "Size"); - } + TableOutput objectTable = null; ClrObject lastFreeObject = default; foreach (ClrObject obj in objects) @@ -77,6 +73,15 @@ namespace Microsoft.Diagnostics.ExtensionCommands ulong size = obj.IsValid ? obj.Size : 0; if (!statsOnly) { + if (objectTable is null) + { + objectTable = new(Console, (12, "x12"), (12, "x12"), (12, ""), (0, "")); + if (displayKind is DisplayKind.Normal or DisplayKind.Strings) + { + objectTable.WriteRow("Address", "MT", "Size"); + } + } + objectTable.WriteRow(new DmlDumpObj(obj), new DmlDumpHeap(obj.Type?.MethodTable ?? 0), size, obj.IsFree ? "Free" : ""); } @@ -192,6 +197,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands } else if (displayKind == DisplayKind.Normal) { + // Print statistics table if (stats.Count != 0) { // Print statistics table diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs new file mode 100644 index 000000000..69f20de9a --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpStackObjectsCommand.cs @@ -0,0 +1,381 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.Runtime; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "dumpstackobjects", Aliases = new string[] { "dso" }, Help = "Displays all managed objects found within the bounds of the current stack.")] + public class DumpStackObjectsCommand : CommandBase + { + [ServiceImport] + public IMemoryService MemoryService { get; set; } + + [ServiceImport] + public IThread CurrentThread { get; set; } + + [ServiceImport] + public IThreadService ThreadService { get; set; } + + [ServiceImport] + public ClrRuntime Runtime { get; set; } + + [Option(Name = "-verify", Help = "Verify each object and only print ones that are valid objects.")] + public bool Verify { get; set; } + + [Argument(Name = "StackBounds", Help = "The top and bottom of the stack (in hex).")] + public string[] Bounds { get; set; } + + public override void Invoke() + { + if (Runtime.Heap.Segments.Length == 0) + { + throw new DiagnosticsException("Cannot walk heap."); + } + + MemoryRange range; + if (Bounds is null || Bounds.Length == 0) + { + range = GetStackRange(); + } + else if (Bounds.Length == 2) + { + ulong start = ParseAddress(Bounds[0]) ?? throw new ArgumentException($"Failed to parse start address '{Bounds[0]}'."); + ulong end = ParseAddress(Bounds[1]) ?? throw new ArgumentException($"Failed to parse end address '{Bounds[1]}'."); + if (start > end) + { + (start, end) = (end, start); + } + + range = new(AlignDown(start), AlignUp(end)); + } + else + { + throw new ArgumentException("Invalid arguments."); + } + + if (range.Start == 0 || range.End == 0) + { + throw new ArgumentException($"Invalid range {range.Start:x} - {range.End:x}"); + } + + PrintStackObjects(range); + } + + private void PrintStackObjects(MemoryRange stack) + { + Console.WriteLine($"OS Thread Id: 0x{CurrentThread.ThreadId:x} ({CurrentThread.ThreadIndex})"); + + TableOutput output = new(Console, (16, "x12"), (16, "x12")); + output.WriteRow("SP/REG", "Object", "Name"); + + int regCount = ThreadService.Registers.Count(); + foreach ((ulong address, ClrObject obj) in EnumerateValidObjectsWithinRange(stack).OrderBy(r => r.StackAddress)) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (address < (ulong)regCount) + { + string registerName; + if (ThreadService.TryGetRegisterInfo((int)address, out RegisterInfo regInfo)) + { + registerName = regInfo.RegisterName; + } + else + { + registerName = $"reg{address}"; + } + + output.WriteRow(registerName, obj.Address, obj.Type?.Name); + } + else + { + output.WriteRow(address, obj.Address, obj.Type?.Name); + } + } + } + + /// + /// Enumerates all valid objects (and the address they came from) within the given range. + /// + private IEnumerable<(ulong StackAddress, ClrObject Object)> EnumerateValidObjectsWithinRange(MemoryRange range) + { + // Note: This implementation is careful to enumerate only real objects and not generate a lot of native + // exceptions within the dac. A naïve implementation could simply read every pointer aligned address + // and call ClrHeap.GetObject(objAddr).IsValid. That approach will generate a lot of exceptions + // within the dac trying to validate wild pointers as MethodTables, and it will often find old + // pointers which the GC has already swept but not zeroed yet. + + // Sort the list of potential objects so that we can go through each in segment order. + // Sorting this array saves us a lot of time by not searching for segments. + IEnumerable<(ulong StackAddress, ulong PotentialObject)> potentialObjects = EnumeratePointersWithinHeapBounds(range); + potentialObjects = potentialObjects.Concat(EnumerateRegistersWithinHeapBounds()); + potentialObjects = potentialObjects.OrderBy(r => r.PotentialObject); + + ClrSegment currSegment = null; + List<(ulong StackAddress, ulong PotentialObject)> withinCurrSegment = new(64); + int segmentIndex = 0; + foreach ((ulong _, ulong PotentialObject) entry in potentialObjects) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + // Find the segment of the current potential object, or null if it doesn't live + // within a segment. + ClrSegment segment = GetSegment(entry.PotentialObject, ref segmentIndex); + if (segment is null) + { + continue; + } + + // If we are already processing this segment, just add the entry to the list + if (currSegment == segment) + { + withinCurrSegment.Add(entry); + continue; + } + + // We are finished walking objects from "currSegment". If we found any pointers + // within its range, walk the segment and return every valid object. + if (withinCurrSegment.Count > 0) + { + foreach ((ulong StackAddress, ClrObject Object) validObject in EnumerateObjectsOnSegment(withinCurrSegment, currSegment)) + { + yield return validObject; + } + + withinCurrSegment.Clear(); + } + + // Update currSegment and add this entry to the processing list. + currSegment = segment; + withinCurrSegment.Add(entry); + } + + // Process leftover items + if (withinCurrSegment.Count > 0) + { + foreach ((ulong StackAddress, ClrObject Object) validObject in EnumerateObjectsOnSegment(withinCurrSegment, currSegment)) + { + yield return validObject; + } + } + } + + /// + /// Simultaneously walks the withinCurrSegment list and objects on segment returning valid objects found. + /// + private IEnumerable<(ulong StackAddress, ClrObject Object)> EnumerateObjectsOnSegment(List<(ulong StackAddress, ulong PotentialObject)> withinCurrSegment, ClrSegment segment) + { + if (withinCurrSegment.Count == 0) + { + yield break; + } + + int index = 0; + MemoryRange range = new(withinCurrSegment[0].PotentialObject, withinCurrSegment[withinCurrSegment.Count - 1].PotentialObject + 1); + foreach (ClrObject obj in segment.EnumerateObjects(range, carefully: true)) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (index >= withinCurrSegment.Count) + { + yield break; + } + + while (index < withinCurrSegment.Count && withinCurrSegment[index].PotentialObject < obj) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + index++; + } + + while (index < withinCurrSegment.Count && obj == withinCurrSegment[index].PotentialObject) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (Verify) + { + if (!Runtime.Heap.IsObjectCorrupted(obj, out _)) + { + yield return (withinCurrSegment[index].StackAddress, obj); + } + } + else + { + yield return (withinCurrSegment[index].StackAddress, obj); + } + + index++; + } + } + } + + private ClrSegment GetSegment(ulong potentialObject, ref int segmentIndex) + { + ImmutableArray segments = Runtime.Heap.Segments; + + // This function assumes that segmentIndex is always within the bounds of segments + // and that all objects passed to it are within the given + // range of segment bounds. + Debug.Assert(segmentIndex >= 0 && segmentIndex <= segments.Length); + Debug.Assert(segments[0].ObjectRange.Start < potentialObject); + Debug.Assert(potentialObject < segments[segments.Length - 1].ObjectRange.End); + + for (; segmentIndex < segments.Length; segmentIndex++) + { + ClrSegment curr = segments[segmentIndex]; + if (potentialObject < curr.Start) + { + return null; + } + else if (potentialObject < curr.ObjectRange.End) + { + return segments[segmentIndex]; + } + } + + // Unreachable. + Debug.Fail("Reached the end of the segment array."); + return null; + } + + private IEnumerable<(ulong RegisterIndex, ulong PotentialObject)> EnumerateRegistersWithinHeapBounds() + { + ClrHeap heap = Runtime.Heap; + + // Segments are always sorted by address + ulong minAddress = heap.Segments[0].ObjectRange.Start; + ulong maxAddress = heap.Segments[heap.Segments.Length - 1].ObjectRange.End - (uint)MemoryService.PointerSize; + + int regCount = ThreadService.Registers.Count(); + for (int i = 0; i < regCount; i++) + { + if (CurrentThread.TryGetRegisterValue(i, out ulong value)) + { + if (minAddress <= value && value < maxAddress) + { + yield return ((ulong)i, value); + } + } + } + } + + private IEnumerable<(ulong StackAddress, ulong PotentialObject)> EnumeratePointersWithinHeapBounds(MemoryRange stack) + { + Debug.Assert(AlignDown(stack.Start) == stack.Start); + Debug.Assert(AlignUp(stack.End) == stack.End); + + uint pointerSize = (uint)MemoryService.PointerSize; + ClrHeap heap = Runtime.Heap; + + // Segments are always sorted by address + ulong minAddress = heap.Segments[0].ObjectRange.Start; + ulong maxAddress = heap.Segments[heap.Segments.Length - 1].ObjectRange.End - pointerSize; + + // Read in 64k chunks + byte[] buffer = ArrayPool.Shared.Rent(64 * 1024); + try + { + ulong address = stack.Start; + while (stack.Contains(address)) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (!MemoryService.ReadMemory(address, buffer, out int read)) + { + break; + } + + read = AlignDown(read); + if (read < pointerSize) + { + break; + } + + for (int i = 0; i < read; i += (int)pointerSize) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + ulong stackAddress = address + (uint)i; + if (!stack.Contains(stackAddress)) + { + yield break; + } + + ulong potentialObj = GetIndex(buffer, i); + if (minAddress <= potentialObj && potentialObj < maxAddress) + { + yield return (stackAddress, potentialObj); + } + } + + address += (uint)read; + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + private static ulong GetIndex(Span buffer, int i) => Unsafe.As(ref buffer[i]); + + private MemoryRange GetStackRange() + { + ulong end = 0; + + int spIndex = ThreadService.StackPointerIndex; + if (!CurrentThread.TryGetRegisterValue(spIndex, out ulong stackPointer)) + { + throw new DiagnosticsException($"Unable to get the stack pointer for thread {CurrentThread.ThreadId:x}."); + } + + // On Windows we have the TEB to know where to end the walk. + ulong teb = CurrentThread.GetThreadTeb(); + if (teb != 0) + { + // The stack base is after the first pointer, see TEB and NT_TIB. + MemoryService.ReadPointer(teb + (uint)MemoryService.PointerSize, out end); + } + + if (end == 0) + { + end = stackPointer + 0xFFFF; + } + + return new(AlignDown(stackPointer), AlignUp(end)); + } + + private ulong AlignDown(ulong address) + { + ulong mask = ~((ulong)MemoryService.PointerSize - 1); + return address & mask; + } + + private int AlignDown(int value) + { + int mask = ~(MemoryService.PointerSize - 1); + return value & mask; + } + + private ulong AlignUp(ulong address) + { + ulong pointerSize = (ulong)MemoryService.PointerSize; + if (address > ulong.MaxValue - pointerSize) + { + return AlignDown(address); + } + + ulong mask = ~(pointerSize - 1); + return (address + pointerSize - 1) & mask; + } + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs index a7fe9e3dd..326f2efef 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/EEHeapCommand.cs @@ -151,7 +151,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands IOrderedEnumerable> filteredHeapsByKind = from heap in appDomain.EnumerateLoaderAllocatorHeaps() where IsIncludedInFilter(heap) - where loaderAllocatorsSeen.Add(heap.Address) + where loaderAllocatorsSeen.Add(heap.MemoryRange.Start) group heap by heap.Kind into g orderby GetSortOrder(g.Key) select g; @@ -244,7 +244,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands { output.WriteRow("JIT Manager:", jitManager.Address); - IEnumerable heaps = jitManager.EnumerateNativeHeaps().Where(IsIncludedInFilter).OrderBy(r => r.Kind).ThenBy(r => r.Address); + IEnumerable heaps = jitManager.EnumerateNativeHeaps().Where(IsIncludedInFilter).OrderBy(r => r.Kind).ThenBy(r => r.MemoryRange.Start); ulong jitMgrSize = 0, jitMgrWasted = 0; foreach (ClrNativeHeapInfo heap in heaps) @@ -284,15 +284,15 @@ namespace Microsoft.Diagnostics.ExtensionCommands return true; } - if (filterRange.Contains(info.Address)) + if (filterRange.Contains(info.MemoryRange.Start)) { return true; } - if (info.Size is ulong size && size > 0) + if (info.MemoryRange.Length > 0) { // Check for the last valid address in the range - return filterRange.Contains(info.Address + size - 1); + return filterRange.Contains(info.MemoryRange.End - 1); } return false; @@ -300,14 +300,15 @@ namespace Microsoft.Diagnostics.ExtensionCommands private (ulong Size, ulong Wasted) CalculateSizeAndWasted(StringBuilder sb, ClrNativeHeapInfo heap) { - sb.Append(heap.Address.ToString("x12")); + sb.Append(heap.MemoryRange.Start.ToString("x12")); - if (heap.Size is ulong size) + ulong size = heap.MemoryRange.Length; + if (size > 0) { sb.Append('('); sb.Append(size.ToString("x")); sb.Append(':'); - ulong actualSize = GetActualSize(heap.Address, size); + ulong actualSize = GetActualSize(heap.MemoryRange.Start, size); sb.Append(actualSize.ToString("x")); sb.Append(')'); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs new file mode 100644 index 000000000..f413b5cb9 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/FinalizeQueueCommand.cs @@ -0,0 +1,224 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.Runtime; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "finalizequeue", Help = "Displays all objects registered for finalization.")] + public class FinalizeQueueCommand : CommandBase + { + [Option(Name = "-detail", Help = "Will display extra information on any SyncBlocks that need to be cleaned up, and on any RuntimeCallableWrappers (RCWs) that await cleanup. Both of these data structures are cached and cleaned up by the finalizer thread when it gets a chance to run.")] + public bool Detail { get; set; } + + [Option(Name = "-allReady", Help = "Specifying this argument will allow for the display of all objects that are ready for finalization, whether they are already marked by the GC as such, or whether the next GC will. The objects that are not in the \"Ready for finalization\" list are finalizable objects that are no longer rooted. This option can be very expensive, as it verifies whether all the objects in the finalizable queues are still rooted or not.")] + public bool AllReady { get; set; } + + [Option(Name = "-short", Help = "Limits the output to just the address of each object. If used in conjunction with -allReady it enumerates all objects that have a finalizer that are no longer rooted. If used independently it lists all objects in the finalizable and \"ready for finalization\" queues.")] + public bool Short { get; set; } + + [Option(Name = "-mt", Help = "Limits the search for finalizable objects to only those matching the given MethodTable.")] + public string MethodTable { get; set; } + + [Option(Name = "-stat", Aliases = new string[] { "-summary" }, Help = "Only print object statistics, not the list of all objects.")] + public bool Stat { get; set; } + + [ServiceImport] + public LiveObjectService LiveObjects { get; set; } + + [ServiceImport] + public RootCacheService RootCache { get; set; } + + [ServiceImport] + public DumpHeapService DumpHeap { get; set; } + + [ServiceImport] + public ClrRuntime Runtime { get; set; } + + public override void Invoke() + { + ulong mt = 0; + if (!string.IsNullOrWhiteSpace(MethodTable)) + { + mt = ParseAddress(MethodTable) ?? throw new ArgumentException($"Could not parse MethodTable: '{MethodTable}'"); + } + + if (Short && Stat) + { + throw new ArgumentException("Cannot specify both -short and -stat."); + } + + // If we are going to search for only live objects, be sure to print a warning first + // in the output of the command instead of in between the rest of the output. + if (AllReady) + { + LiveObjects.PrintWarning = true; + LiveObjects.Initialize(); + } + + if (!Short) + { + PrintSyncBlockCleanupData(); + PrintRcwCleanupData(); + Console.WriteLine("----------------------------------"); + Console.WriteLine(); + + PrintGenerationalRanges(); + + if (AllReady) + { + Console.WriteLine("Statistics for all finalizable objects that are no longer rooted:"); + } + else + { + Console.WriteLine("Statistics for all finalizable objects (including all objects ready for finalization):"); + } + } + + IEnumerable objects = EnumerateFinalizableObjects(AllReady, mt); + DumpHeapService.DisplayKind displayKind = Short ? DumpHeapService.DisplayKind.Short : DumpHeapService.DisplayKind.Normal; + + DumpHeap.PrintHeap(objects, displayKind, Stat, printFragmentation: false); + + } + private IEnumerable EnumerateFinalizableObjects(bool allReady, ulong mt) + { + IEnumerable result = EnumerateValidFinalizableObjectsWithTypeFilter(mt); + + if (allReady) + { + HashSet rootedByFinalizer = new(); + foreach (ClrRoot root in Runtime.Heap.EnumerateFinalizerRoots()) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + ClrObject obj = root.Object; + if (obj.IsValid) + { + rootedByFinalizer.Add(obj); + } + } + + // We are trying to find all objects that are ready to be finalized, which is essentially + // all dead objects. However, objects which were previously collected but waiting on + // the finalizer thread to process them are considered "live" because they are rooted by + // the finalizer queue. So our result needs to be either dead objects or directly rooted + // by the finalizer queue. + result = result.Where(obj => rootedByFinalizer.Contains(obj) || !LiveObjects.IsLive(obj)); + } + + return result; + } + + private IEnumerable EnumerateValidFinalizableObjectsWithTypeFilter(ulong mt) + { + foreach (ClrObject obj in Runtime.Heap.EnumerateFinalizableObjects()) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (!obj.IsValid) + { + continue; + } + + if (mt != 0 && obj.Type.MethodTable != mt) + { + continue; + } + + yield return obj; + } + } + + private void PrintSyncBlockCleanupData() + { + TableOutput output = null; + int total = 0; + foreach (ClrSyncBlockCleanupData cleanup in Runtime.EnumerateSyncBlockCleanupData()) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (output is null) + { + output = new(Console, (16, "x12"), (16, "x12"), (16, "x12")); + output.WriteRow("SyncBlock", "RCW", "CCW", "ComClassFactory"); + } + + output.WriteRow(cleanup.SyncBlock, cleanup.Rcw, cleanup.Ccw, cleanup.ClassFactory); + total++; + } + + Console.WriteLine($"SyncBlocks to be cleaned up: {total:n0}"); + } + + private void PrintRcwCleanupData() + { + TableOutput output = null; + int freeThreadedCount = 0; + int mtaCount = 0; + int staCount = 0; + + foreach (ClrRcwCleanupData cleanup in Runtime.EnumerateRcwCleanupData()) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + if (output is null) + { + output = new(Console, (16, "x12"), (16, "x12"), (16, "x12")); + output.WriteRow("RCW", "Context", "Thread", "Apartment"); + } + + string apartment; + if (cleanup.IsFreeThreaded) + { + freeThreadedCount++; + apartment = "(FreeThreaded)"; + } + else if (cleanup.Thread == 0) + { + mtaCount++; + apartment = "(MTA)"; + } + else + { + staCount++; + apartment = "(STA)"; + } + + output.WriteRow(cleanup.Rcw, cleanup.Context, cleanup.Thread, apartment); + } + + Console.WriteLine($"Free-Threaded Interfaces to be released: {freeThreadedCount:n0}"); + Console.WriteLine($"MTA Interfaces to be released: {mtaCount:n0}"); + Console.WriteLine($"STA Interfaces to be released: {staCount:n0}"); + } + + private void PrintGenerationalRanges() + { + foreach (ClrSubHeap heap in Runtime.Heap.SubHeaps) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + + Console.WriteLine($"Heap {heap.Index}"); + + WriteGeneration(heap, 0); + WriteGeneration(heap, 1); + WriteGeneration(heap, 2); + + Console.WriteLine($"Ready for finalization {heap.FinalizerQueueRoots.Length / (uint)IntPtr.Size:n0} objects ({heap.FinalizerQueueRoots.Start:x}->{heap.FinalizerQueueRoots.End:x})"); + + Console.WriteLine("------------------------------"); + } + } + + private void WriteGeneration(ClrSubHeap heap, int gen) + { + MemoryRange range = heap.GenerationalFinalizableObjects[gen]; + Console.WriteLine($"generation {gen} has {range.Length / (uint)IntPtr.Size:n0} objects ({range.Start:x}->{range.End:x})"); + } + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs index 4edb69349..abe025273 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCRootCommand.cs @@ -61,6 +61,22 @@ namespace Microsoft.Diagnostics.ExtensionCommands if (AsGCGeneration.HasValue) { int gen = AsGCGeneration.Value; + + ClrSegment seg = Runtime.Heap.GetSegmentByAddress(address); + if (seg is null) + { + Console.WriteLineError($"Address {address:x} is not in the managed heap."); + return; + } + + Generation objectGen = seg.GetGeneration(address); + if (gen < (int)objectGen) + { + Console.WriteLine($"Object {address:x} will survive this collection:"); + Console.WriteLine($" gen({address:x}) = {objectGen} > {gen} = condemned generation."); + return; + } + if (gen < 0 || gen > 1) { // If not gen0 or gen1, treat it as a normal !gcroot diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/LiveObjectService.cs b/src/Microsoft.Diagnostics.ExtensionCommands/LiveObjectService.cs index ed39e0de0..a77b997eb 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/LiveObjectService.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/LiveObjectService.cs @@ -35,6 +35,11 @@ namespace Microsoft.Diagnostics.ExtensionCommands return _liveObjs.Contains(obj); } + public void Initialize() + { + _liveObjs ??= CreateObjectSet(); + } + private HashSet CreateObjectSet() { ClrHeap heap = Runtime.Heap; diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs index 2f48a8133..9658d026e 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/NativeAddressHelper.cs @@ -13,10 +13,19 @@ using Microsoft.Diagnostics.Runtime; namespace Microsoft.Diagnostics.ExtensionCommands { [ServiceExport(Scope = ServiceScope.Target)] - public sealed class NativeAddressHelper + public sealed class NativeAddressHelper : IDisposable { + private readonly IDisposable _onFlushEvent; private ((bool, bool, bool, bool) Key, DescribedRegion[] Result) _previous; + public NativeAddressHelper(ITarget target) + { + Target = target; + _onFlushEvent = target.OnFlushEvent.Register(() => _previous = default); + } + + public void Dispose() => _onFlushEvent.Dispose(); + [ServiceImport] public ITarget Target { get; set; } @@ -301,7 +310,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands _ => ClrMemoryKind.Unknown }; - yield return (nativeHeap.Address, nativeHeap.Size ?? 0, kind); + yield return (nativeHeap.MemoryRange.Start, nativeHeap.MemoryRange.Length, kind); } // .Net 8 and beyond has accurate HandleTable memory info. diff --git a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs index d398b1945..1190fd29a 100644 --- a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs +++ b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs @@ -27,12 +27,10 @@ namespace SOS.Hosting [Command(Name = "dumpobj", DefaultOptions = "DumpObj", Aliases = new string[] { "do" }, Help = "Displays info about an object at the specified address.")] [Command(Name = "dumpsig", DefaultOptions = "DumpSig", Help = "Dumps the signature of a method or field specified by .")] [Command(Name = "dumpsigelem", DefaultOptions = "DumpSigElem", Help = "Dumps a single element of a signature object.")] - [Command(Name = "dumpstackobjects", DefaultOptions = "DumpStackObjects", Aliases = new string[] { "dso" }, Help = "Displays all managed objects found within the bounds of the current stack.")] [Command(Name = "dumpvc", DefaultOptions = "DumpVC", Help = "Displays info about the fields of a value class.")] [Command(Name = "eeversion", DefaultOptions = "EEVersion", Help = "Displays information about the runtime version.")] [Command(Name = "ehinfo", DefaultOptions = "EHInfo", Help = "Displays the exception handling blocks in a JIT-ed method.")] [Command(Name = "enummem", DefaultOptions = "enummem", Help = "ICLRDataEnumMemoryRegions.EnumMemoryRegions test command.")] - [Command(Name = "finalizequeue", DefaultOptions = "FinalizeQueue", Help = "Displays all objects registered for finalization.")] [Command(Name = "findappdomain", DefaultOptions = "FindAppDomain", Help = "Attempts to resolve the AppDomain of a GC object.")] [Command(Name = "gchandles", DefaultOptions = "GCHandles", Help = "Provides statistics about GCHandles in the process.")] [Command(Name = "gcinfo", DefaultOptions = "GCInfo", Help = "Displays JIT GC encoding for a method.")] diff --git a/src/SOS/SOS.UnitTests/SOS.cs b/src/SOS/SOS.UnitTests/SOS.cs index ae55c129b..4332a63da 100644 --- a/src/SOS/SOS.UnitTests/SOS.cs +++ b/src/SOS/SOS.UnitTests/SOS.cs @@ -340,8 +340,7 @@ public class SOS // This debuggee needs the directory of the exes/dlls to load the SymbolTestDll assembly. await SOSTestHelpers.RunTest( - scriptName: "StackAndOtherTests.script", - new SOSRunner.TestInformation + scriptName: "StackAndOtherTests.script", new SOSRunner.TestInformation { TestConfiguration = currentConfig, TestName = "SOS.StackAndOtherTests", diff --git a/src/SOS/SOS.UnitTests/SOSRunner.cs b/src/SOS/SOS.UnitTests/SOSRunner.cs index e1f993f3b..98366fb0d 100644 --- a/src/SOS/SOS.UnitTests/SOSRunner.cs +++ b/src/SOS/SOS.UnitTests/SOSRunner.cs @@ -145,6 +145,8 @@ public class SOSRunner : IDisposable public string DumpNameSuffix { get; set; } + public bool EnableSOSLogging { get; set; } = true; + public bool TestCrashReport { get { return _testCrashReport && DumpGenerator == DumpGenerator.CreateDump && OS.Kind != OSKind.Windows && TestConfiguration.RuntimeFrameworkVersionMajor >= 6; } @@ -457,6 +459,7 @@ public class SOSRunner : IDisposable { // Setup the logging from the options in the config file outputHelper = TestRunner.ConfigureLogging(config, information.OutputHelper, information.TestName); + string sosLogFile = information.EnableSOSLogging ? Path.Combine(config.LogDirPath, $"{information.TestName}.{config.LogSuffix}.soslog") : null; // Figure out which native debugger to use NativeDebugger debugger = GetNativeDebuggerToUse(config, action); @@ -669,6 +672,7 @@ public class SOSRunner : IDisposable break; } + // Create the native debugger process running ProcessRunner processRunner = new ProcessRunner(debuggerPath, ReplaceVariables(variables, arguments.ToString())). WithEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"). @@ -682,6 +686,11 @@ public class SOSRunner : IDisposable processRunner.WithExpectedExitCode(0); } + if (sosLogFile != null) + { + processRunner.WithEnvironmentVariable("DOTNET_ENABLED_SOS_LOGGING", sosLogFile); + } + // Disable W^E so that the bpmd command and the tests pass // Issue: https://github.com/dotnet/diagnostics/issues/3126 processRunner.WithRuntimeConfiguration("EnableWriteXorExecute", "0"); @@ -1420,6 +1429,11 @@ public class SOSRunner : IDisposable defines.Add("UNIX_SINGLE_FILE_APP"); } } + string setHostRuntime = _config.SetHostRuntime(); + if (!string.IsNullOrEmpty(setHostRuntime) && setHostRuntime == "-none") + { + defines.Add("HOST_RUNTIME_NONE"); + } return defines; } diff --git a/src/SOS/SOS.UnitTests/Scripts/GCPOH.script b/src/SOS/SOS.UnitTests/Scripts/GCPOH.script index 4873d7037..2c223a6bf 100644 --- a/src/SOS/SOS.UnitTests/Scripts/GCPOH.script +++ b/src/SOS/SOS.UnitTests/Scripts/GCPOH.script @@ -18,7 +18,7 @@ ENDIF:CDB LOADSOS SOSCOMMAND:DumpStackObjects -VERIFY:\s+\s+System.Byte\[\]\s+ +VERIFY:\s*\s+\s+System.Byte\[\]\s+ SOSCOMMAND:DumpObj \w+\s+()\s+(System.Byte\[\]!\$0_)*System.Byte\[\]\s+ VERIFY:\s+Name: System.Byte\[\]\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/GCTests.script b/src/SOS/SOS.UnitTests/Scripts/GCTests.script index 96f558f78..38be904a9 100644 --- a/src/SOS/SOS.UnitTests/Scripts/GCTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/GCTests.script @@ -24,7 +24,7 @@ ENDIF:CDB LOADSOS SOSCOMMAND:DumpStackObjects -VERIFY:\s+\s+System.IO.StringWriter\s+ +VERIFY:\s*\s+\s+System.IO.StringWriter\s+ SOSCOMMAND:DumpObj \w+\s+()\s+(System.IO.StringWriter!\$0_)*System.IO.StringWriter\s+ IFDEF:MAJOR_RUNTIME_VERSION_2 @@ -36,7 +36,7 @@ VERIFY:\s+\s+\s+\s+System\.IO.TextWriter\s+\s+st ENDIF:MAJOR_RUNTIME_VERSION_GE_3 SOSCOMMAND:DumpStackObjects -VERIFY:\s\s([Gg][Cc]where!\$0_)*GCWhere\s+ +VERIFY:\s*\s+\s+([Gg][Cc]where!\$0_)*GCWhere\s+ SOSCOMMAND:GCWhere \w+\s+()\s+([Gg][Cc]where!\$0_)*GCWhere\s+ # we care that the Gen is 0 diff --git a/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script b/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script index 62e5c9b35..8274219dc 100644 --- a/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script +++ b/src/SOS/SOS.UnitTests/Scripts/OtherCommands.script @@ -126,9 +126,9 @@ SOSCOMMAND:FinalizeQueue VERIFY:\s*SyncBlocks to be cleaned up:\s+\s+ VERIFY:(\s*Free-Threaded Interfaces to be released:\s+\s+)? VERIFY:\s*Statistics for all finalizable objects.*:\s+ -VERIFY:\s+MT\s+Count\s+TotalSize\s+Class Name\s+ +VERIFY:\s+Address\s+MT\s+Size\s+ VERIFY:(\s*\s+\s+\s+.*)? -VERIFY:\s*Total\s+\s+objects\s+ +VERIFY:\s*Total\s+\s+objects.*\s+bytes\s* EXTCOMMAND:logopen %LOG_PATH%/%TEST_NAME%.%LOG_SUFFIX%.consolelog EXTCOMMAND:logging %LOG_PATH%/%TEST_NAME%.%LOG_SUFFIX%.diaglog diff --git a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script index 685b0d2ed..5c31e0ef1 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script @@ -180,19 +180,25 @@ VERIFY:.*\s+\s+\s+\[DEFAULT\] I4 SymbolTestApp\.Program\.Foo1\(. VERIFY:.*\s+\s+\s+\[DEFAULT\] Void SymbolTestApp\.Program\.Main\(.*\)\s+\(.*\)\s+ VERIFY:.*\s+Stack walk complete.\s+ +SOSCOMMAND: runtimes + +!IFDEF:HOST_RUNTIME_NONE + # Verify DumpStackObjects works SOSCOMMAND:DumpStackObjects VERIFY:.*OS Thread Id:\s+0x\s+.* -VERIFY:\s+([R|E])*SP/REG\s+Object\s+Name\s+ -VERIFY:.*\s+\s+\s+System\.String.* -VERIFY:.*\s+\s+\s+System\.String\[\].* +VERIFY:\s*SP/REG\s+Object\s+Name\s+ +VERIFY:.*\s*\s+\s+System\.String.* +VERIFY:.*\s*\s+\s+System\.String\[\].* # Verify DumpStackObjects -verify works SOSCOMMAND:DumpStackObjects -verify VERIFY:.*OS Thread Id:\s+0x\s+.* -VERIFY:\s+([R|E])*SP/REG\s+Object\s+Name\s+ -VERIFY:.*\s+\s+\s+System\.String.* -VERIFY:.*\s+\s+\s+System\.String\[\].* +VERIFY:\s*SP/REG\s+Object\s+Name\s+ +VERIFY:.*\s*\s+\s+System\.String.* +VERIFY:.*\s*\s+\s+System\.String\[\].* + +ENDIF:HOST_RUNTIME_NONE ENDIF:NETCORE_OR_DOTNETDUMP diff --git a/src/SOS/SOS.UnitTests/Scripts/StackTests.script b/src/SOS/SOS.UnitTests/Scripts/StackTests.script index 57a5d87ee..002a019e0 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackTests.script @@ -137,7 +137,7 @@ ENDIF:NETCORE_OR_DOTNETDUMP # 7) Verify DumpStackObjects works SOSCOMMAND:DumpStackObjects VERIFY:.*OS Thread Id:\s+0x\s+.* -VERIFY:\s+([R|E])*SP/REG\s+Object\s+Name\s+ +VERIFY:\s*SP/REG\s+Object\s+Name\s+ VERIFY:.*\s+\s+\s+System\.FormatException\s+ VERIFY:.*\s+\s+\s+System\.InvalidOperationException\s+ VERIFY:.*\s+\s+\s+System\.String.* @@ -145,7 +145,7 @@ VERIFY:.*\s+\s+\s+System\.String.* # 8) Verify DumpStackObjects -verify works SOSCOMMAND:DumpStackObjects -verify VERIFY:.*OS Thread Id:\s+0x\s+.* -VERIFY:\s+([R|E])*SP/REG\s+Object\s+Name\s+ +VERIFY:\s*SP/REG\s+Object\s+Name\s+ VERIFY:.*\s+\s+\s+System\.FormatException\s+ VERIFY:.*\s+\s+\s+System\.InvalidOperationException\s+ VERIFY:.*\s+\s+\s+System\.String.* diff --git a/src/SOS/Strike/disasm.cpp b/src/SOS/Strike/disasm.cpp index 482f48f85..10590c165 100644 --- a/src/SOS/Strike/disasm.cpp +++ b/src/SOS/Strike/disasm.cpp @@ -1039,7 +1039,6 @@ void DumpStackWorker (DumpStackFlag &DSFlag) /// X86Machine implementation /// LPCSTR X86Machine::s_DumpStackHeading = "ChildEBP RetAddr Caller, Callee\n"; -LPCSTR X86Machine::s_DSOHeading = "ESP/REG Object Name\n"; LPCSTR X86Machine::s_GCRegs[7] = {"eax", "ebx", "ecx", "edx", "esi", "edi", "ebp"}; LPCSTR X86Machine::s_SPName = "ESP"; @@ -1081,7 +1080,6 @@ void X86Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, printf /// ARMMachine implementation /// LPCSTR ARMMachine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n"; -LPCSTR ARMMachine::s_DSOHeading = "SP/REG Object Name\n"; LPCSTR ARMMachine::s_GCRegs[14] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "lr"}; LPCSTR ARMMachine::s_SPName = "sp"; @@ -1093,7 +1091,6 @@ LPCSTR ARMMachine::s_SPName = "sp"; /// AMD64Machine implementation /// LPCSTR AMD64Machine::s_DumpStackHeading = "Child-SP RetAddr Caller, Callee\n"; -LPCSTR AMD64Machine::s_DSOHeading = "RSP/REG Object Name\n"; LPCSTR AMD64Machine::s_GCRegs[15] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}; LPCSTR AMD64Machine::s_SPName = "RSP"; @@ -1121,7 +1118,6 @@ void AMD64Machine::DumpGCInfo(GCInfoToken gcInfoToken, unsigned methodSize, prin /// ARM64Machine implementation /// LPCSTR ARM64Machine::s_DumpStackHeading = "ChildFP RetAddr Caller, Callee\n"; -LPCSTR ARM64Machine::s_DSOHeading = "SP/REG Object Name\n"; // excluding x18, fp & lr as these will not contain object references LPCSTR ARM64Machine::s_GCRegs[28] = {"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", diff --git a/src/SOS/Strike/disasm.h b/src/SOS/Strike/disasm.h index 0bacb2b17..a09e5abec 100644 --- a/src/SOS/Strike/disasm.h +++ b/src/SOS/Strike/disasm.h @@ -171,7 +171,6 @@ public: virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } virtual LPCSTR GetSPName() const { return s_SPName; } virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = ARRAY_SIZE(s_GCRegs); } @@ -188,7 +187,6 @@ private: private: static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; static LPCSTR s_GCRegs[7]; static LPCSTR s_SPName; }; // class X86Machine @@ -243,7 +241,6 @@ public: virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } virtual LPCSTR GetSPName() const { return s_SPName; } virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = ARRAY_SIZE(s_GCRegs); } @@ -260,7 +257,6 @@ private: private: static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; static LPCSTR s_GCRegs[14]; static LPCSTR s_SPName; static ARMMachine s_ARMMachineInstance; @@ -316,7 +312,6 @@ public: virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } virtual LPCSTR GetSPName() const { return s_SPName; } virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = ARRAY_SIZE(s_GCRegs); } @@ -333,7 +328,6 @@ private: private: static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; static LPCSTR s_GCRegs[15]; static LPCSTR s_SPName; }; // class AMD64Machine @@ -386,7 +380,6 @@ public: virtual void FillTargetContext(LPVOID destCtx, LPVOID srcCtx, int idx = 0) const; virtual LPCSTR GetDumpStackHeading() const { return s_DumpStackHeading; } - virtual LPCSTR GetDumpStackObjectsHeading() const { return s_DSOHeading; } virtual LPCSTR GetSPName() const { return s_SPName; } virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const { _ASSERTE(cntRegs != NULL); *regNames = s_GCRegs; *cntRegs = ARRAY_SIZE(s_GCRegs);} @@ -402,7 +395,6 @@ private: ARM64Machine & operator=(const ARM64Machine&); // undefined static LPCSTR s_DumpStackHeading; - static LPCSTR s_DSOHeading; static LPCSTR s_GCRegs[28]; static LPCSTR s_SPName; diff --git a/src/SOS/Strike/eeheap.cpp b/src/SOS/Strike/eeheap.cpp index 3b89f7023..259cb4299 100644 --- a/src/SOS/Strike/eeheap.cpp +++ b/src/SOS/Strike/eeheap.cpp @@ -529,440 +529,3 @@ BOOL GetCollectibleDataEfficient(DWORD_PTR dwAddrMethTable, BOOL& bCollectible, return TRUE; } - -// This function expects stat to be valid, and ready to get statistics. -void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort) -{ - DWORD_PTR dwAddr=0; - UINT m; - - if (!bShort) - { - for (m = 0; m <= GetMaxGeneration(); m ++) - { - if (IsInterrupt()) - return; - - ExtOut("generation %d has %d finalizable objects ", m, - (SegQueueLimit(heapDetails,gen_segment(m)) - SegQueue(heapDetails,gen_segment(m))) / sizeof(size_t)); - - ExtOut ("(%p->%p)\n", - SOS_PTR(SegQueue(heapDetails,gen_segment(m))), - SOS_PTR(SegQueueLimit(heapDetails,gen_segment(m)))); - } - } - - if (bAllReady) - { - if (!bShort) - { - ExtOut ("Finalizable but not rooted: "); - } - - TADDR rngStart = (TADDR)SegQueue(heapDetails, gen_segment(GetMaxGeneration())); - TADDR rngEnd = (TADDR)SegQueueLimit(heapDetails, gen_segment(0)); - - std::stringstream argsBuilder; - argsBuilder << std::hex << rngStart << " "; - argsBuilder << std::hex << rngEnd << " "; - argsBuilder << "-nofinalizer "; - if (bShort) - argsBuilder << "-short"; - - ExecuteCommand("notreachableinrange", argsBuilder.str().c_str()); - } - - if (!bShort) - { - ExtOut ("Ready for finalization %d objects ", - (SegQueueLimit(heapDetails,FinalizerListSeg)-SegQueue(heapDetails,CriticalFinalizerListSeg)) / sizeof(size_t)); - ExtOut ("(%p->%p)\n", - SOS_PTR(SegQueue(heapDetails,CriticalFinalizerListSeg)), - SOS_PTR(SegQueueLimit(heapDetails,FinalizerListSeg))); - } - - // if bAllReady we only count objects that are ready for finalization, - // otherwise we count all finalizable objects. - TADDR taddrLowerLimit = (bAllReady ? (TADDR)SegQueue(heapDetails, CriticalFinalizerListSeg) : - (DWORD_PTR)SegQueue(heapDetails, gen_segment(GetMaxGeneration()))); - for (dwAddr = taddrLowerLimit; - dwAddr < (DWORD_PTR)SegQueueLimit(heapDetails, FinalizerListSeg); - dwAddr += sizeof (dwAddr)) - { - if (IsInterrupt()) - { - return; - } - - DWORD_PTR objAddr = NULL, - MTAddr = NULL; - - if (SUCCEEDED(MOVE(objAddr, dwAddr)) && SUCCEEDED(GetMTOfObject(objAddr, &MTAddr)) && MTAddr) - { - if (bShort) - { - DMLOut("%s\n", DMLObject(objAddr)); - } - else - { - size_t s = ObjectSize(objAddr); - stat->Add(MTAddr, (DWORD)s); - } - } - } -} - -GCHeapSnapshot::GCHeapSnapshot() -{ - m_isBuilt = FALSE; - m_heapDetails = NULL; -} - -/////////////////////////////////////////////////////////// -SegmentLookup::SegmentLookup() -{ - m_iSegmentsSize = m_iSegmentCount = 0; - - m_segments = new DacpHeapSegmentData[nSegLookupStgIncrement]; - if (m_segments == NULL) - { - ReportOOM(); - } - else - { - m_iSegmentsSize = nSegLookupStgIncrement; - } -} - -BOOL SegmentLookup::AddSegment(DacpHeapSegmentData *pData) -{ - // appends the address of a new (initialized) instance of DacpHeapSegmentData to the list of segments - // (m_segments) adding space for a segment when necessary. - // @todo Microsoft: The field name m_iSegmentSize is a little misleading. It's not the size in bytes, - // but the number of elements allocated for the array. It probably should have been named something like - // m_iMaxSegments instead. - if (m_iSegmentCount >= m_iSegmentsSize) - { - // expand buffer--allocate enough space to hold the elements we already have plus nSegLookupStgIncrement - // more elements - DacpHeapSegmentData *pNewBuffer = new DacpHeapSegmentData[m_iSegmentsSize+nSegLookupStgIncrement]; - if (pNewBuffer==NULL) - return FALSE; - - // copy the old elements into the new array - memcpy(pNewBuffer, m_segments, sizeof(DacpHeapSegmentData)*m_iSegmentsSize); - - // record the new number of elements available - m_iSegmentsSize+=nSegLookupStgIncrement; - - // delete the old array - delete [] m_segments; - - // set m_segments to point to the new array - m_segments = pNewBuffer; - } - - // add pData to the array - m_segments[m_iSegmentCount++] = *pData; - - return TRUE; -} - -SegmentLookup::~SegmentLookup() -{ - if (m_segments) - { - delete [] m_segments; - m_segments = NULL; - } -} - -void SegmentLookup::Clear() -{ - m_iSegmentCount = 0; -} - -CLRDATA_ADDRESS SegmentLookup::GetHeap(CLRDATA_ADDRESS object, BOOL& bFound) -{ - CLRDATA_ADDRESS ret = NULL; - bFound = FALSE; - - // Visit our segments - for (int i=0; i TO_TADDR(object)) - { - ret = m_segments[i].gc_heap; - bFound = TRUE; - break; - } - } - - return ret; -} - -/////////////////////////////////////////////////////////////////////////// - -BOOL GCHeapSnapshot::Build() -{ - Clear(); - - m_isBuilt = FALSE; - - ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// 1. Get some basic information such as the heap type (SVR or WKS), how many heaps there are, mode and max generation - /// (See code:ClrDataAccess::RequestGCHeapData) - ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (m_gcheap.Request(g_sos) != S_OK) - { - ExtOut("Error requesting GC Heap data\n"); - return FALSE; - } - - ArrayHolder heapAddrs = NULL; - - ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// 2. Get a list of the addresses of the heaps when we have multiple heaps in server mode - ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (m_gcheap.bServerMode) - { - UINT AllocSize; - // allocate an array to hold the starting addresses of each heap when we're in server mode - if (!ClrSafeInt::multiply(sizeof(CLRDATA_ADDRESS), m_gcheap.HeapCount, AllocSize) || - (heapAddrs = new CLRDATA_ADDRESS [m_gcheap.HeapCount]) == NULL) - { - ReportOOM(); - return FALSE; - } - - // and initialize it with their addresses (see code:ClrDataAccess::RequestGCHeapList - // for details) - if (g_sos->GetGCHeapList(m_gcheap.HeapCount, heapAddrs, NULL) != S_OK) - { - ExtOut("Failed to get GCHeaps\n"); - return FALSE; - } - } - - ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// 3. Get some necessary information about each heap, such as the card table location, the generation - /// table, the heap bounds, etc., and retrieve the heap segments - ///- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // allocate an array to hold the information - m_heapDetails = new GCHeapDetails[m_gcheap.HeapCount]; - - if (m_heapDetails == NULL) - { - ReportOOM(); - return FALSE; - } - - // get the heap information for each heap - // See code:ClrDataAccess::RequestGCHeapDetails for details - for (UINT n = 0; n < m_gcheap.HeapCount; n ++) - { - if (m_gcheap.bServerMode) - { - DacpGcHeapDetails dacHeapDetails; - if (dacHeapDetails.Request(g_sos, heapAddrs[n]) != S_OK) - { - ExtOut("Error requesting details\n"); - return FALSE; - } - - m_heapDetails[n].Set(dacHeapDetails, heapAddrs[n]); - } - else - { - DacpGcHeapDetails dacHeapDetails; - if (dacHeapDetails.Request(g_sos) != S_OK) - { - ExtOut("Error requesting details\n"); - return FALSE; - } - - m_heapDetails[n].Set(dacHeapDetails); - } - - // now get information about the heap segments for this heap - if (!AddSegments(m_heapDetails[n])) - { - ExtOut("Failed to retrieve segments for gc heap\n"); - return FALSE; - } - } - - m_isBuilt = TRUE; - return TRUE; -} - -BOOL GCHeapSnapshot::AddSegments(const GCHeapDetails& details) -{ - int n = 0; - DacpHeapSegmentData segment; - - // This array of addresses gives us access to all the segments. - CLRDATA_ADDRESS AddrSegs[5]; - if (details.has_regions) - { - // with regions, each generation has its own list of segments - for (unsigned gen = 0; gen <= GetMaxGeneration() + 1; gen++) - { - AddrSegs[gen] = details.generation_table[gen].start_segment; - } - if (details.has_poh) - { - AddrSegs[4] = details.generation_table[GetMaxGeneration() + 2].start_segment; // pinned object heap - } - } - else - { - // The generation segments are linked to each other, starting with the maxGeneration segment. - // The second address gives us the large object heap, the third the pinned object heap - - AddrSegs[0] = details.generation_table[GetMaxGeneration()].start_segment; - AddrSegs[1] = details.generation_table[GetMaxGeneration() + 1].start_segment; - AddrSegs[2] = NULL; - if (details.has_poh) - { - AddrSegs[2] = details.generation_table[GetMaxGeneration() + 2].start_segment; // pinned object heap - } - AddrSegs[3] = NULL; - AddrSegs[4] = NULL; - } - - // this loop will get information for all the heap segments in this heap. The outer loop iterates once - // for the "normal" generation segments and once for the large object heap. The inner loop follows the chain - // of segments rooted at AddrSegs[i] - for (unsigned int i = 0; i < ARRAY_SIZE(AddrSegs); ++i) - { - if (AddrSegs[i] == NULL) - { - continue; - } - - CLRDATA_ADDRESS AddrSeg = AddrSegs[i]; - - while (AddrSeg != NULL) - { - if (IsInterrupt()) - { - return FALSE; - } - // Initialize segment by copying fields from the target's heap segment at AddrSeg. - // See code:ClrDataAccess::RequestGCHeapSegment for details. - if (segment.Request(g_sos, AddrSeg, details.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(AddrSeg)); - return FALSE; - } - // add the new segment to the array of segments. This will expand the array if necessary - if (!m_segments.AddSegment(&segment)) - { - ExtOut("strike: Failed to store segment\n"); - return FALSE; - } - // get the next segment in the chain - AddrSeg = segment.next; - } - } - - return TRUE; -} - -void GCHeapSnapshot::Clear() -{ - if (m_heapDetails != NULL) - { - delete [] m_heapDetails; - m_heapDetails = NULL; - } - - m_segments.Clear(); - - m_isBuilt = FALSE; -} - -GCHeapSnapshot g_snapshot; - -GCHeapDetails *GCHeapSnapshot::GetHeap(CLRDATA_ADDRESS objectPointer) -{ - // We need bFound because heap will be NULL if we are Workstation Mode. - // We still need a way to know if the address was found in our segment - // list. - BOOL bFound = FALSE; - CLRDATA_ADDRESS heap = m_segments.GetHeap(objectPointer, bFound); - if (heap) - { - for (UINT i=0; ihas_regions) - { - for (int gen_num = 0; gen_num <= 1; gen_num++) - { - CLRDATA_ADDRESS dwAddrSeg = pDetails->generation_table[gen_num].start_segment; - while (dwAddrSeg != 0) - { - DacpHeapSegmentData segment; - if (segment.Request(g_sos, dwAddrSeg, pDetails->original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg)); - return 0; - } - // The DAC doesn't fill the generation table with true CLRDATA_ADDRESS values - // but rather with ULONG64 values (i.e. non-sign-extended 64-bit values) - // We use the TO_TADDR below to ensure we won't break if this will ever - // be fixed in the DAC. - if (TO_TADDR(segment.mem) <= taObj && taObj < TO_TADDR(segment.highAllocMark)) - { - return gen_num; - } - dwAddrSeg = segment.next; - } - } - } - else - { - // The DAC doesn't fill the generation table with true CLRDATA_ADDRESS values - // but rather with ULONG64 values (i.e. non-sign-extended 64-bit values) - // We use the TO_TADDR below to ensure we won't break if this will ever - // be fixed in the DAC. - if (taObj >= TO_TADDR(pDetails->generation_table[0].allocation_start) && - taObj <= TO_TADDR(pDetails->alloc_allocated)) - return 0; - - if (taObj >= TO_TADDR(pDetails->generation_table[1].allocation_start) && - taObj <= TO_TADDR(pDetails->generation_table[0].allocation_start)) - return 1; - } - return 2; -} diff --git a/src/SOS/Strike/exts.cpp b/src/SOS/Strike/exts.cpp index c72c91829..3ebe83fde 100644 --- a/src/SOS/Strike/exts.cpp +++ b/src/SOS/Strike/exts.cpp @@ -409,3 +409,14 @@ HRESULT GetRuntime(IRuntime** ppRuntime) #endif return target->GetRuntime(ppRuntime); } + +void FlushCheck() +{ +#ifndef FEATURE_PAL + SOSExtensions* extensions = (SOSExtensions*)Extensions::GetInstance(); + if (extensions != nullptr) + { + extensions->FlushCheck(); + } +#endif // !FEATURE_PAL +} diff --git a/src/SOS/Strike/exts.h b/src/SOS/Strike/exts.h index 71916ec45..cf29ce595 100644 --- a/src/SOS/Strike/exts.h +++ b/src/SOS/Strike/exts.h @@ -192,6 +192,7 @@ public: }; extern HRESULT GetRuntime(IRuntime** ppRuntime); +extern void FlushCheck(); #ifndef MINIDUMP @@ -322,7 +323,8 @@ inline void DACMessage(HRESULT Status) ControlC = FALSE; \ g_bDacBroken = TRUE; \ g_clrData = NULL; \ - g_sos = NULL; + g_sos = NULL; \ + FlushCheck(); // Also initializes the target machine #define INIT_API_NOEE() \ @@ -464,7 +466,6 @@ public: // Retrieve some target specific output strings virtual LPCSTR GetDumpStackHeading() const = 0; - virtual LPCSTR GetDumpStackObjectsHeading() const = 0; virtual LPCSTR GetSPName() const = 0; // Retrieves the non-volatile registers reported to the GC virtual void GetGCRegisters(LPCSTR** regNames, unsigned int* cntRegs) const = 0; diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index f0efe93e8..20feea9e7 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -390,6 +390,7 @@ HRESULT ExecuteCommand(PCSTR commandName, PCSTR args) return hostServices->DispatchCommand(commandName, args); } } + ExtErr("Unrecognized command %s\n", commandName); return E_NOTIMPL; } @@ -616,82 +617,6 @@ DECLARE_API (EEStack) return Status; } -HRESULT DumpStackObjectsRaw(size_t nArg, __in_z LPSTR exprBottom, __in_z LPSTR exprTop, BOOL bVerify) -{ - size_t StackTop = 0; - size_t StackBottom = 0; - if (nArg==0) - { - ULONG64 StackOffset; - g_ExtRegisters->GetStackOffset(&StackOffset); - - StackTop = TO_TADDR(StackOffset); - } - else - { - StackTop = GetExpression(exprTop); - if (StackTop == 0) - { - ExtOut("wrong option: %s\n", exprTop); - return E_FAIL; - } - - if (nArg==2) - { - StackBottom = GetExpression(exprBottom); - if (StackBottom == 0) - { - ExtOut("wrong option: %s\n", exprBottom); - return E_FAIL; - } - } - } - -#ifndef FEATURE_PAL - if (IsWindowsTarget()) - { - NT_TIB teb; - ULONG64 dwTebAddr = 0; - HRESULT hr = g_ExtSystem->GetCurrentThreadTeb(&dwTebAddr); - if (SUCCEEDED(hr) && SafeReadMemory(TO_TADDR(dwTebAddr), &teb, sizeof(NT_TIB), NULL)) - { - if (StackTop > TO_TADDR(teb.StackLimit) && StackTop <= TO_TADDR(teb.StackBase)) - { - if (StackBottom == 0 || StackBottom > TO_TADDR(teb.StackBase)) - StackBottom = TO_TADDR(teb.StackBase); - } - } - } -#endif - - if (StackBottom == 0) - StackBottom = StackTop + 0xFFFF; - - if (StackBottom < StackTop) - { - ExtOut("Wrong option: stack selection wrong\n"); - return E_FAIL; - } - - // We can use the gc snapshot to eliminate object addresses that are - // not on the gc heap. - if (!g_snapshot.Build()) - { - ExtOut("Unable to determine bounds of gc heap\n"); - return E_FAIL; - } - - // Print thread ID. - ULONG id = 0; - g_ExtSystem->GetCurrentThreadSystemId (&id); - ExtOut("OS Thread Id: 0x%x ", id); - g_ExtSystem->GetCurrentThreadId (&id); - ExtOut("(%d)\n", id); - - DumpStackObjectsHelper(StackTop, StackBottom, bVerify); - return S_OK; -} - /**********************************************************************\ * Routine Description: * * * @@ -701,32 +626,10 @@ HRESULT DumpStackObjectsRaw(size_t nArg, __in_z LPSTR exprBottom, __in_z LPSTR e \**********************************************************************/ DECLARE_API(DumpStackObjects) { - INIT_API(); + INIT_API_EXT(); MINIDUMP_NOT_SUPPORTED(); - StringHolder exprTop, exprBottom; - - BOOL bVerify = FALSE; - BOOL dml = FALSE; - CMDOption option[] = - { // name, vptr, type, hasValue - {"-verify", &bVerify, COBOOL, FALSE}, - {"/d", &dml, COBOOL, FALSE} - }; - CMDValue arg[] = - { // vptr, type - {&exprTop.data, COSTRING}, - {&exprBottom.data, COSTRING} - }; - size_t nArg; - - if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) - { - return Status; - } - EnableDMLHolder enableDML(dml); - - return DumpStackObjectsRaw(nArg, exprBottom.data, exprTop.data, bVerify); + return ExecuteCommand("dumpstackobjects", args); } /**********************************************************************\ @@ -890,13 +793,7 @@ DECLARE_API(DumpIL) return DecodeILFromAddress(NULL, dwStartAddr); } - if (!g_snapshot.Build()) - { - ExtOut("Unable to build snapshot of the garbage collector state\n"); - return Status; - } - - if (g_snapshot.GetHeap(dwStartAddr) != NULL) + if (sos::IsObject(dwStartAddr)) { dwDynamicMethodObj = dwStartAddr; } @@ -4027,138 +3924,10 @@ DECLARE_API(RCWCleanupList) \**********************************************************************/ DECLARE_API(FinalizeQueue) { - INIT_API(); + INIT_API_EXT(); MINIDUMP_NOT_SUPPORTED(); - BOOL bDetail = FALSE; - BOOL bAllReady = FALSE; - BOOL bShort = FALSE; - BOOL dml = FALSE; - TADDR taddrMT = 0; - - CMDOption option[] = - { // name, vptr, type, hasValue - {"-detail", &bDetail, COBOOL, FALSE}, - {"-allReady", &bAllReady, COBOOL, FALSE}, - {"-short", &bShort, COBOOL, FALSE}, - {"/d", &dml, COBOOL, FALSE}, - {"-mt", &taddrMT, COHEX, TRUE}, - }; - - if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) - { - return Status; - } - - EnableDMLHolder dmlHolder(dml); - if (!bShort) - { - DacpSyncBlockCleanupData dsbcd; - CLRDATA_ADDRESS sbCurrent = NULL; - ULONG cleanCount = 0; - while ((dsbcd.Request(g_sos,sbCurrent) == S_OK) && dsbcd.SyncBlockPointer) - { - if (bDetail) - { - if (cleanCount == 0) // print first time only - { - ExtOut("SyncBlocks to be cleaned by the finalizer thread:\n"); - ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s\n", - "SyncBlock", "RCW", "CCW", "ComClassFactory"); - } - - ExtOut("%" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p %" POINTERSIZE "p\n", - (ULONG64) dsbcd.SyncBlockPointer, - (ULONG64) dsbcd.blockRCW, - (ULONG64) dsbcd.blockCCW, - (ULONG64) dsbcd.blockClassFactory); - } - - cleanCount++; - sbCurrent = dsbcd.nextSyncBlock; - if (sbCurrent == NULL) - { - break; - } - } - - ExtOut("SyncBlocks to be cleaned up: %d\n", cleanCount); - -#ifdef FEATURE_COMINTEROP - VisitRcwArgs travArgs; - ZeroMemory(&travArgs,sizeof(VisitRcwArgs)); - travArgs.bDetail = bDetail; - g_sos->TraverseRCWCleanupList(0, (VISITRCWFORCLEANUP) VisitRcw, &travArgs); - ExtOut("Free-Threaded Interfaces to be released: %d\n", travArgs.FTMCount); - ExtOut("MTA Interfaces to be released: %d\n", travArgs.MTACount); - ExtOut("STA Interfaces to be released: %d\n", travArgs.STACount); -#endif // FEATURE_COMINTEROP - -// noRCW: - ExtOut("----------------------------------\n"); - } - - // GC Heap - DWORD dwNHeaps = GetGcHeapCount(); - - HeapStat hpStat; - - if (!IsServerBuild()) - { - DacpGcHeapDetails heapDetails; - if (heapDetails.Request(g_sos) != S_OK) - { - ExtOut("Error requesting details\n"); - return Status; - } - - GatherOneHeapFinalization(heapDetails, &hpStat, bAllReady, bShort); - } - else - { - DWORD dwAllocSize; - if (!ClrSafeInt::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) - { - ExtOut("Failed to get GCHeaps: integer overflow\n"); - return Status; - } - - CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); - if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) - { - ExtOut("Failed to get GCHeaps\n"); - return Status; - } - - for (DWORD n = 0; n < dwNHeaps; n ++) - { - DacpGcHeapDetails heapDetails; - if (heapDetails.Request(g_sos, heapAddrs[n]) != S_OK) - { - ExtOut("Error requesting details\n"); - return Status; - } - - ExtOut("------------------------------\n"); - ExtOut("Heap %d\n", n); - - GatherOneHeapFinalization(heapDetails, &hpStat, bAllReady, bShort); - } - } - - if (!bShort) - { - if (bAllReady) - { - PrintGCStat(&hpStat, "Statistics for all finalizable objects that are no longer rooted:\n"); - } - else - { - PrintGCStat(&hpStat, "Statistics for all finalizable objects (including all objects ready for finalization):\n"); - } - } - - return Status; + return ExecuteCommand("finalizequeue", args); } enum { @@ -9020,26 +8789,6 @@ DECLARE_API(FindRoots) ExtOut("At this time %sgcroot should be used instead.\n", SOSPrefix); return Status; } - // validate argument - if (!g_snapshot.Build()) - { - ExtOut("Unable to build snapshot of the garbage collector state\n"); - return Status; - } - - if (g_snapshot.GetHeap(taObj) == NULL) - { - ExtOut("Address %#p is not in the managed heap.\n", SOS_PTR(taObj)); - return Status; - } - - int ogen = g_snapshot.GetGeneration(taObj); - if (ogen > CNotification::GetCondemnedGen()) - { - DMLOut("Object %s will survive this collection:\n\tgen(%#p) = %d > %d = condemned generation.\n", - DMLObject(taObj), SOS_PTR(taObj), ogen, CNotification::GetCondemnedGen()); - return Status; - } std::stringstream argsBuilder; argsBuilder << "-gcgen " << CNotification::GetCondemnedGen() << " " << std::hex << taObj; diff --git a/src/SOS/Strike/util.cpp b/src/SOS/Strike/util.cpp index 768c919ca..8ef8d8344 100644 --- a/src/SOS/Strike/util.cpp +++ b/src/SOS/Strike/util.cpp @@ -1927,109 +1927,6 @@ BOOL TryGetMethodDescriptorForDelegate(CLRDATA_ADDRESS delegateAddr, CLRDATA_ADD return FALSE; } -void DumpStackObjectsOutput(const char *location, DWORD_PTR objAddr, BOOL verifyFields) -{ - // rule out pointers that are outside of the gc heap. - if (g_snapshot.GetHeap(objAddr) == NULL) - return; - - DacpObjectData objectData; - if (objectData.Request(g_sos, TO_CDADDR(objAddr)) != S_OK) - return; - - if (sos::IsObject(objAddr, verifyFields != FALSE) - && !sos::MethodTable::IsFreeMT(TO_TADDR(objectData.MethodTable))) - { - DMLOut("%-" POINTERSIZE "s %s ", location, DMLObject(objAddr)); - if (g_sos->GetObjectClassName(TO_CDADDR(objAddr), mdNameLen, g_mdName, NULL)==S_OK) - { - ExtOut("%S", g_mdName); - - if (IsStringObject(objAddr)) - { - ExtOut(" "); - StringObjectContent(objAddr, FALSE, 40); - } - else if (IsObjectArray(objAddr) && - (g_sos->GetMethodTableName(objectData.ElementTypeHandle, mdNameLen, g_mdName, NULL) == S_OK)) - { - ExtOut(" "); - ExtOut("(%S[])", g_mdName); - } - } - else - { - ExtOut(""); - } - ExtOut("\n"); - } -} - -void DumpStackObjectsOutput(DWORD_PTR ptr, DWORD_PTR objAddr, BOOL verifyFields) -{ - char location[64]; - sprintf_s(location, 64, "%p", (DWORD_PTR *)ptr); - - DumpStackObjectsOutput(location, objAddr, verifyFields); -} - -void DumpStackObjectsInternal(size_t StackTop, size_t StackBottom, BOOL verifyFields) -{ - for (DWORD_PTR ptr = StackTop; ptr <= StackBottom; ptr += sizeof(DWORD_PTR)) - { - if (IsInterrupt()) - return; - - DWORD_PTR objAddr; - move_xp(objAddr, ptr); - - DumpStackObjectsOutput(ptr, objAddr, verifyFields); - } -} - -void DumpRegObjectHelper(const char *regName, BOOL verifyFields) -{ - DWORD_PTR reg; -#ifdef FEATURE_PAL - if (FAILED(g_ExtRegisters->GetValueByName(regName, ®))) - return; -#else - DEBUG_VALUE value; - ULONG IREG; - if (FAILED(g_ExtRegisters->GetIndexByName(regName, &IREG)) || - FAILED(g_ExtRegisters->GetValue(IREG, &value))) - return; - -#if defined(SOS_TARGET_X86) || defined(SOS_TARGET_ARM) - reg = (DWORD_PTR) value.I32; -#elif defined(SOS_TARGET_AMD64) || defined(SOS_TARGET_ARM64) - reg = (DWORD_PTR) value.I64; -#else -#error Unsupported target -#endif -#endif // FEATURE_PAL - - DumpStackObjectsOutput(regName, reg, verifyFields); -} - -void DumpStackObjectsHelper ( - TADDR StackTop, - TADDR StackBottom, - BOOL verifyFields) -{ - ExtOut(g_targetMachine->GetDumpStackObjectsHeading()); - - LPCSTR* regs; - unsigned int cnt; - g_targetMachine->GetGCRegisters(®s, &cnt); - - for (size_t i = 0; i < cnt; ++i) - DumpRegObjectHelper(regs[i], verifyFields); - - // Make certain StackTop is dword aligned: - DumpStackObjectsInternal(StackTop & ~ALIGNCONST, StackBottom, verifyFields); -} - void AddToModuleList(DWORD_PTR * &moduleList, int &numModule, int &maxList, DWORD_PTR dwModuleAddr) { @@ -3437,24 +3334,6 @@ BOOL GetSOSVersion(VS_FIXEDFILEINFO *pFileInfo) #endif // !FEATURE_PAL -size_t ObjectSize(DWORD_PTR obj,BOOL fIsLargeObject) -{ - DWORD_PTR dwMT; - MOVE(dwMT, obj); - return ObjectSize(obj, dwMT, FALSE, fIsLargeObject); -} - -size_t ObjectSize(DWORD_PTR obj, DWORD_PTR mt, BOOL fIsValueClass, BOOL fIsLargeObject) -{ - BOOL bContainsPointers; - size_t size = 0; - if (!GetSizeEfficient(obj, mt, fIsLargeObject, size, bContainsPointers)) - { - return 0; - } - return size; -} - // This takes an array of values and sets every non-printable character // to be a period. void Flatten(__out_ecount(len) char *data, unsigned int len) @@ -4080,83 +3959,6 @@ BOOL GetGcStructuresValid() return heapData.bGcStructuresValid; } -void GetAllocContextPtrs(AllocInfo *pallocInfo) -{ - // gets the allocation contexts for all threads. This provides information about how much of - // the current allocation quantum has been allocated and the heap to which the quantum belongs. - // The allocation quantum is a fixed size chunk of zeroed memory from which allocations will come - // until it's filled. Each managed thread has its own allocation context. - - pallocInfo->num = 0; - pallocInfo->array = NULL; - - // get the thread store (See code:ClrDataAccess::RequestThreadStoreData for details) - DacpThreadStoreData ThreadStore; - if ( ThreadStore.Request(g_sos) != S_OK) - { - return; - } - - int numThread = ThreadStore.threadCount; - if (numThread) - { - pallocInfo->array = new needed_alloc_context[numThread + 1]; - if (pallocInfo->array == NULL) - { - return; - } - } - - // get details for each thread in the thread store - CLRDATA_ADDRESS CurThread = ThreadStore.firstThread; - while (CurThread != NULL) - { - if (IsInterrupt()) - return; - - DacpThreadData Thread; - // Get information about the thread (we're getting the values of several of the - // fields of the Thread instance from the target) See code:ClrDataAccess::RequestThreadData for - // details - if (Thread.Request(g_sos, CurThread) != S_OK) - { - return; - } - - if (Thread.allocContextPtr != 0) - { - // get a list of all the allocation contexts - int j; - for (j = 0; j < pallocInfo->num; j ++) - { - if (pallocInfo->array[j].alloc_ptr == (BYTE *) Thread.allocContextPtr) - break; - } - if (j == pallocInfo->num) - { - pallocInfo->num ++; - pallocInfo->array[j].alloc_ptr = (BYTE *) Thread.allocContextPtr; - pallocInfo->array[j].alloc_limit = (BYTE *) Thread.allocContextLimit; - } - } - - CurThread = Thread.nextThread; - } - - CLRDATA_ADDRESS allocPtr; - CLRDATA_ADDRESS allocLimit; - - ReleaseHolder sos12; - if (SUCCEEDED(g_sos->QueryInterface(__uuidof(ISOSDacInterface12), &sos12)) && - SUCCEEDED(sos12->GetGlobalAllocationContext(&allocPtr, &allocLimit)) && - allocPtr != 0) - { - int j = pallocInfo->num ++; - pallocInfo->array[j].alloc_ptr = (BYTE *) allocPtr; - pallocInfo->array[j].alloc_limit = (BYTE *) allocLimit; - } -} - HRESULT ReadVirtualCache::Read(TADDR address, PVOID buffer, ULONG bufferSize, PULONG lpcbBytesRead) { // address can be any random ULONG64, as it can come from VerifyObjectMember(), and this @@ -5824,88 +5626,3 @@ HRESULT GetMetadataMemory(CLRDATA_ADDRESS address, ULONG32 bufferSize, BYTE* buf } #endif // FEATURE_PAL - -/////////////////////////////////////////////////////////////////////////////////////////// -// -// Miscellaneous helper methods -// - -void EnumerateThreadPoolGlobalWorkItemConcurrentQueue( - DWORD_PTR workItemsConcurrentQueuePtr, - const char *queueName, - HeapStat *stats) -{ - // Get its head segment. - sos::Object workItemsConcurrentQueue = TO_TADDR(workItemsConcurrentQueuePtr); - int offset = GetObjFieldOffset(workItemsConcurrentQueue.GetAddress(), workItemsConcurrentQueue.GetMT(), W("_head")); - if (offset <= 0) - { - return; - } - - // Now, walk from segment to segment, each of which contains an array of work items. - DWORD_PTR segmentPtr; - MOVE(segmentPtr, workItemsConcurrentQueue.GetAddress() + offset); - while (sos::IsObject(segmentPtr, false)) - { - sos::Object segment = TO_TADDR(segmentPtr); - - // Get the work items array. It's an array of Slot structs, which starts with the T. - offset = GetObjFieldOffset(segment.GetAddress(), segment.GetMT(), W("_slots")); - if (offset <= 0) - { - break; - } - - DWORD_PTR slotsPtr; - MOVE(slotsPtr, segment.GetAddress() + offset); - if (!sos::IsObject(slotsPtr, false)) - { - break; - } - - // Walk every element in the array, outputting details on non-null work items. - DacpObjectData slotsArray; - if (slotsArray.Request(g_sos, TO_CDADDR(slotsPtr)) == S_OK && slotsArray.ObjectType == OBJ_ARRAY) - { - for (int i = 0; i < slotsArray.dwNumComponents; i++) - { - DWORD_PTR workItemPtr; - MOVE(workItemPtr, slotsArray.ArrayDataPtr + (i * slotsArray.dwComponentSize)); // the item object reference is at the beginning of the Slot - if (workItemPtr != NULL && sos::IsObject(TO_CDADDR(workItemPtr), false)) - { - sos::Object workItem = TO_TADDR(workItemPtr); - stats->Add((DWORD_PTR)workItem.GetMT(), (DWORD)workItem.GetSize()); - DMLOut("%" THREAD_POOL_WORK_ITEM_TABLE_QUEUE_WIDTH "s %s %S", queueName, DMLObject(workItem.GetAddress()), workItem.GetTypeName()); - if ((offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("_callback"))) > 0 || - (offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("m_action"))) > 0) - { - DWORD_PTR delegatePtr; - MOVE(delegatePtr, workItem.GetAddress() + offset); - CLRDATA_ADDRESS md; - if (TryGetMethodDescriptorForDelegate(TO_CDADDR(delegatePtr), &md)) - { - NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen); - ExtOut(" => %S", g_mdName); - } - } - ExtOut("\n"); - } - } - } - - // Move to the next segment. - DacpFieldDescData segmentField; - offset = GetObjFieldOffset(segment.GetAddress(), segment.GetMT(), W("_nextSegment"), TRUE, &segmentField); - if (offset <= 0) - { - break; - } - - MOVE(segmentPtr, segment.GetAddress() + offset); - if (segmentPtr == NULL) - { - break; - } - } -} diff --git a/src/SOS/Strike/util.h b/src/SOS/Strike/util.h index 2fb1eb0be..50467dd8a 100644 --- a/src/SOS/Strike/util.h +++ b/src/SOS/Strike/util.h @@ -262,215 +262,6 @@ private: static StaticData cache; }; -class GCHeapDetails -{ -private: - void GetGenerationTableSize(CLRDATA_ADDRESS svrHeapAddr, unsigned int *count) - { - HRESULT hr = S_OK; - bool success = false; - ReleaseHolder sos8; - if (!SUCCEEDED(hr = g_sos->QueryInterface(__uuidof(ISOSDacInterface8), &sos8)) - || !SUCCEEDED(hr = sos8->GetNumberGenerations(count))) - { - // The runtime will either have the original 4 generations or implement ISOSDacInterface8 - // if the call succeeded, count is already populated. - *count = DAC_NUMBERGENERATIONS; - } - } - - // Fill the target array with either the details from heap or if this is a newer runtime that supports - // the pinned object heap (or potentially future GC generations), get that data too. This abstraction is - // necessary because the original GC heap APIs are hardcoded to 4 generations. - void FillGenerationTable(CLRDATA_ADDRESS svrHeapAddr, const DacpGcHeapDetails &heap, unsigned int count, DacpGenerationData *data) - { - HRESULT hr = S_OK; - bool success = false; - unsigned int generationCount; - ReleaseHolder sos8; - if (SUCCEEDED(hr = g_sos->QueryInterface(__uuidof(ISOSDacInterface8), &sos8))) - { - if (svrHeapAddr == NULL) - { - if (SUCCEEDED(hr = sos8->GetGenerationTable(count, data, &generationCount)) - && hr != S_FALSE) - { - success = true; - // Nothing else to do, data is already populated - } - } - else - { - if (SUCCEEDED(hr = sos8->GetGenerationTableSvr(svrHeapAddr, count, data, &generationCount)) - && hr != S_FALSE) - { - success = true; - // Nothing else to do, data is already populated - } - } - - _ASSERTE(generationCount == count || !success); - } - - if (!success) - { - // This would mean that there are additional, unaccounted for, generations - _ASSERTE(hr != S_FALSE); - - // We couldn't get any data from the newer APIs, so fall back to the original data - memcpy(data, &(heap.generation_table), sizeof(DacpGenerationData) * DAC_NUMBERGENERATIONS); - } - } - - // Fill the target array with either the details from heap or if this is a newer runtime that supports - // the pinned object heap (or potentially future GC generations), get that data too. This abstraction is - // necessary because the original GC heap APIs are hardcoded to 4 generations. - void FillFinalizationPointers(CLRDATA_ADDRESS svrHeapAddr, const DacpGcHeapDetails &heap, unsigned int count, CLRDATA_ADDRESS *data) - { - HRESULT hr = S_OK; - bool success = false; - unsigned int fillPointersCount; - ReleaseHolder sos8; - if (SUCCEEDED(hr = g_sos->QueryInterface(__uuidof(ISOSDacInterface8), &sos8))) - { - if (svrHeapAddr == NULL) - { - if (SUCCEEDED(hr = sos8->GetFinalizationFillPointers(count, data, &fillPointersCount)) - && hr != S_FALSE) - { - success = true; - // Nothing else to do, data is already populated - } - } - else - { - if (SUCCEEDED(hr = sos8->GetFinalizationFillPointersSvr(svrHeapAddr, count, data, &fillPointersCount)) - && hr != S_FALSE) - { - success = true; - // Nothing else to do, data is already populated - } - } - - _ASSERTE(fillPointersCount == count); - } - - if (!success) - { - // This would mean that there are additional, unaccounted for, generations - _ASSERTE(hr != S_FALSE); - - // We couldn't get any data from the newer APIs, so fall back to the original data - memcpy(data, &(heap.finalization_fill_pointers), sizeof(CLRDATA_ADDRESS) * (DAC_NUMBERGENERATIONS + 2)); - } - } - -public: - GCHeapDetails() - { - generation_table = NULL; - finalization_fill_pointers = NULL; - } - - GCHeapDetails(const DacpGcHeapDetails &dacGCDetails, CLRDATA_ADDRESS svrHeapAddr = NULL) - { - generation_table = NULL; - finalization_fill_pointers = NULL; - - Set(dacGCDetails, svrHeapAddr); - } - - ~GCHeapDetails() - { - if (generation_table != NULL) - { - delete[] generation_table; - generation_table = NULL; - } - - if (finalization_fill_pointers != NULL) - { - delete[] finalization_fill_pointers; - finalization_fill_pointers = NULL; - } - } - - // Due to the raw pointers, we are not a POD and have to be careful about lifetime - GCHeapDetails(const GCHeapDetails& other) = delete; - GCHeapDetails(GCHeapDetails&& other) = delete; - GCHeapDetails& operator=(const GCHeapDetails& other) = delete; - GCHeapDetails& operator=(GCHeapDetails&& other) = delete; - - void Set(const DacpGcHeapDetails dacGCDetails, CLRDATA_ADDRESS svrHeapAddr = NULL) - { - original_heap_details = dacGCDetails; - - GetGenerationTableSize(svrHeapAddr, &num_generations); - // Either we're pre POH and have 4, or post and have 5. If there's a different - // number it's either a bug or we need to update SOS. - _ASSERTE(num_generations == 4 || num_generations == 5); - has_poh = num_generations > 4; - - if (generation_table != NULL) - { - delete[] generation_table; - } - generation_table = new DacpGenerationData[num_generations]; - FillGenerationTable(svrHeapAddr, dacGCDetails, num_generations, generation_table); - - if (finalization_fill_pointers != NULL) - { - delete[] finalization_fill_pointers; - } - - unsigned int num_fill_pointers = num_generations + 2; - finalization_fill_pointers = new CLRDATA_ADDRESS[num_fill_pointers]; - FillFinalizationPointers(svrHeapAddr, dacGCDetails, num_fill_pointers, finalization_fill_pointers); - - heapAddr = svrHeapAddr; - alloc_allocated = dacGCDetails.alloc_allocated; - mark_array = dacGCDetails.mark_array; - current_c_gc_state = dacGCDetails.current_c_gc_state; - next_sweep_obj = dacGCDetails.next_sweep_obj; - saved_sweep_ephemeral_seg = dacGCDetails.saved_sweep_ephemeral_seg; - saved_sweep_ephemeral_start = dacGCDetails.saved_sweep_ephemeral_start; - background_saved_lowest_address = dacGCDetails.background_saved_lowest_address; - background_saved_highest_address = dacGCDetails.background_saved_highest_address; - ephemeral_heap_segment = dacGCDetails.ephemeral_heap_segment; - lowest_address = dacGCDetails.lowest_address; - highest_address = dacGCDetails.highest_address; - card_table = dacGCDetails.card_table; - has_regions = generation_table[0].start_segment != generation_table[1].start_segment; - has_background_gc = dacGCDetails.mark_array != -1; - } - - DacpGcHeapDetails original_heap_details; - bool has_poh; - bool has_regions; - bool has_background_gc; - CLRDATA_ADDRESS heapAddr; // Only filled in in server mode, otherwise NULL - CLRDATA_ADDRESS alloc_allocated; - - CLRDATA_ADDRESS mark_array; - CLRDATA_ADDRESS current_c_gc_state; - CLRDATA_ADDRESS next_sweep_obj; - CLRDATA_ADDRESS saved_sweep_ephemeral_seg; - CLRDATA_ADDRESS saved_sweep_ephemeral_start; - CLRDATA_ADDRESS background_saved_lowest_address; - CLRDATA_ADDRESS background_saved_highest_address; - - // There are num_generations entries in generation_table and num_generations + 3 entries - // in finalization_fill_pointers - unsigned int num_generations; - DacpGenerationData *generation_table; - CLRDATA_ADDRESS ephemeral_heap_segment; - CLRDATA_ADDRESS *finalization_fill_pointers; - CLRDATA_ADDRESS lowest_address; - CLRDATA_ADDRESS highest_address; - CLRDATA_ADDRESS card_table; - -}; - // Things in this namespace should not be directly accessed/called outside of // the output-related functions. namespace Output @@ -1915,33 +1706,6 @@ struct DumpArrayFlags HRESULT GetMTOfObject(TADDR obj, TADDR *mt); -struct needed_alloc_context -{ - BYTE* alloc_ptr; // starting point for next allocation - BYTE* alloc_limit; // ending point for allocation region/quantum -}; - -struct AllocInfo -{ - needed_alloc_context *array; - int num; // number of allocation contexts in array - - AllocInfo() - : array(NULL) - , num(0) - {} - void Init() - { - extern void GetAllocContextPtrs(AllocInfo *pallocInfo); - GetAllocContextPtrs(this); - } - ~AllocInfo() - { - if (array != NULL) - delete[] array; - } -}; - struct GCHandleStatistics { HeapStat hs; @@ -1968,47 +1732,6 @@ struct GCHandleStatistics } }; -struct SegmentLookup -{ - DacpHeapSegmentData *m_segments; - int m_iSegmentsSize; - int m_iSegmentCount; - - SegmentLookup(); - ~SegmentLookup(); - - void Clear(); - BOOL AddSegment(DacpHeapSegmentData *pData); - CLRDATA_ADDRESS GetHeap(CLRDATA_ADDRESS object, BOOL& bFound); -}; - -class GCHeapSnapshot -{ -private: - BOOL m_isBuilt; - GCHeapDetails *m_heapDetails; - DacpGcHeapData m_gcheap; - SegmentLookup m_segments; - - BOOL AddSegments(const GCHeapDetails& details); -public: - GCHeapSnapshot(); - - BOOL Build(); - void Clear(); - BOOL IsBuilt() { return m_isBuilt; } - - DacpGcHeapData *GetHeapData() { return &m_gcheap; } - - int GetHeapCount() { return m_gcheap.HeapCount; } - - GCHeapDetails *GetHeap(CLRDATA_ADDRESS objectPointer); - int GetGeneration(CLRDATA_ADDRESS objectPointer); - - -}; -extern GCHeapSnapshot g_snapshot; - BOOL IsSameModuleName (const char *str1, const char *str2); BOOL IsModule (DWORD_PTR moduleAddr); BOOL IsMethodDesc (DWORD_PTR value); @@ -2050,33 +1773,10 @@ struct strobjInfo DWORD m_StringLength; }; -// Just to make figuring out which fill pointer element matches a generation -// a bit less confusing. This gen_segment function is ported from gc.cpp. -inline unsigned int gen_segment (int gen) -{ - return (DAC_NUMBERGENERATIONS - gen - 1); -} - -inline CLRDATA_ADDRESS SegQueue(DacpGcHeapDetails& heapDetails, int seg) -{ - return heapDetails.finalization_fill_pointers[seg - 1]; -} - -inline CLRDATA_ADDRESS SegQueueLimit(DacpGcHeapDetails& heapDetails, int seg) -{ - return heapDetails.finalization_fill_pointers[seg]; -} - -#define FinalizerListSeg (DAC_NUMBERGENERATIONS+1) -#define CriticalFinalizerListSeg (DAC_NUMBERGENERATIONS) - -void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, BOOL bAllReady, BOOL bShort); - CLRDATA_ADDRESS GetAppDomainForMT(CLRDATA_ADDRESS mtPtr); CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr); BOOL IsMTForFreeObj(DWORD_PTR pMT); -void DumpStackObjectsHelper (TADDR StackTop, TADDR StackBottom, BOOL verifyFields); HRESULT ExecuteCommand(PCSTR commandName, PCSTR args); @@ -2102,7 +1802,6 @@ void DumpMDInfoFromMethodDescData(DacpMethodDescData * pMethodDescData, BOOL fSt void GetDomainList(DWORD_PTR *&domainList, int &numDomain); HRESULT GetThreadList(DWORD_PTR **threadList, int *numThread); CLRDATA_ADDRESS GetCurrentManagedThread(); // returns current managed thread if any -void GetAllocContextPtrs(AllocInfo *pallocInfo); void ReloadSymbolWithLineInfo(); @@ -2122,10 +1821,6 @@ BOOL GetSizeEfficient(DWORD_PTR dwAddrCurrObj, BOOL GetCollectibleDataEfficient(DWORD_PTR dwAddrMethTable, BOOL& bCollectible, TADDR& loaderAllocatorObjectHandle); -// ObjSize now uses the methodtable cache for its work too. -size_t ObjectSize (DWORD_PTR obj, BOOL fIsLargeObject=FALSE); -size_t ObjectSize(DWORD_PTR obj, DWORD_PTR mt, BOOL fIsValueClass, BOOL fIsLargeObject=FALSE); - void CharArrayContent(TADDR pos, ULONG num, bool widechar); void StringObjectContent (size_t obj, BOOL fLiteral=FALSE, const int length=-1); // length=-1: dump everything in the string object. @@ -2906,15 +2601,4 @@ private: }; #include "sigparser.h" -/////////////////////////////////////////////////////////////////////////////////////////// -// -// Miscellaneous helper methods -// - -#define THREAD_POOL_WORK_ITEM_TABLE_QUEUE_WIDTH "17" -void EnumerateThreadPoolGlobalWorkItemConcurrentQueue( - DWORD_PTR workItemsConcurrentQueuePtr, - const char *queueName, - HeapStat *stats); - #endif // __util_h__ diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 218577830..7f79ec8bc 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -181,14 +181,14 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("dumpsig", new sosCommand("DumpSig"), "Dumps the signature of a method or field specified by ' '."); g_services->AddCommand("dumpsigelem", new sosCommand("DumpSigElem"), "Dumps a single element of a signature object."); g_services->AddCommand("dumpstack", new sosCommand("DumpStack"), "Displays a native and managed stack trace."); - g_services->AddCommand("dumpstackobjects", new sosCommand("DumpStackObjects"), "Displays all managed objects found within the bounds of the current stack."); - g_services->AddCommand("dso", new sosCommand("DumpStackObjects"), "Displays all managed objects found within the bounds of the current stack."); + g_services->AddManagedCommand("dumpstackobjects", "Displays all managed objects found within the bounds of the current stack."); + g_services->AddManagedCommand("dso", "Displays all managed objects found within the bounds of the current stack."); g_services->AddCommand("dumpvc", new sosCommand("DumpVC"), "Displays info about the fields of a value class."); g_services->AddManagedCommand("eeheap", "Displays info about process memory consumed by internal runtime data structures."); g_services->AddCommand("eestack", new sosCommand("EEStack"), "Runs dumpstack on all threads in the process."); g_services->AddCommand("eeversion", new sosCommand("EEVersion"), "Displays information about the runtime and SOS versions."); g_services->AddCommand("ehinfo", new sosCommand("EHInfo"), "Displays the exception handling blocks in a JIT-ed method."); - g_services->AddCommand("finalizequeue", new sosCommand("FinalizeQueue"), "Displays all objects registered for finalization."); + g_services->AddManagedCommand("finalizequeue", "Displays all objects registered for finalization."); g_services->AddCommand("findappdomain", new sosCommand("FindAppDomain"), "Attempts to resolve the AppDomain of a GC object."); g_services->AddCommand("findroots", new sosCommand("FindRoots"), "Finds and displays object roots across GC collections."); g_services->AddCommand("gchandles", new sosCommand("GCHandles"), "Displays statistics about garbage collector handles in the process.");