Improve/reimplement !listnearobj and !gcwhere (#3748)
authorLee Culver <leculver@microsoft.com>
Wed, 15 Mar 2023 00:49:20 +0000 (17:49 -0700)
committerGitHub <noreply@github.com>
Wed, 15 Mar 2023 00:49:20 +0000 (17:49 -0700)
* Update ClrMD version

* Add !gcwhere, !listnearobj commands

* Update ListNearObjCommand.cs

* Use C# versions, remove dead code

* Fix command name

* Output fixes

* Fix build warning

* Test fixes

13 files changed:
eng/Version.Details.xml
eng/Versions.props
src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs
src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs
src/SOS/SOS.Hosting/Commands/SOSCommand.cs
src/SOS/SOS.UnitTests/Scripts/GCPOH.script
src/SOS/SOS.UnitTests/Scripts/GCTests.script
src/SOS/Strike/eeheap.cpp
src/SOS/Strike/strike.cpp
src/SOS/Strike/util.h

index 71b882a439d6635bed622a5656d32615becc299e..069175810cafae3f01eb9aa12352c456b61152ff 100644 (file)
@@ -1,16 +1,16 @@
 <Dependencies>
   <ProductDependencies>
-    <Dependency Name="Microsoft.SymbolStore" Version="1.0.416301">
+    <Dependency Name="Microsoft.SymbolStore" Version="1.0.415602">
       <Uri>https://github.com/dotnet/symstore</Uri>
-      <Sha>2cf03f4bfea0712e28b0ef1df419898337abf304</Sha>
+      <Sha>5678da4ea42b6f48c0156be1bd5dfe85e67850d9</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.Diagnostics.Runtime" Version="3.0.0-beta.23163.4">
+    <Dependency Name="Microsoft.Diagnostics.Runtime" Version="3.0.0-beta.23164.1">
       <Uri>https://github.com/microsoft/clrmd</Uri>
-      <Sha>62aa8232f49e5a199385d9da3adbde32a3e8b3e2</Sha>
+      <Sha>58e151bd3faa99efa5fce6ca4e63f346a5e11906</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.Diagnostics.Runtime.Utilities" Version="3.0.0-beta.23163.4">
+    <Dependency Name="Microsoft.Diagnostics.Runtime.Utilities" Version="3.0.0-beta.23164.1">
       <Uri>https://github.com/microsoft/clrmd</Uri>
-      <Sha>62aa8232f49e5a199385d9da3adbde32a3e8b3e2</Sha>
+      <Sha>58e151bd3faa99efa5fce6ca4e63f346a5e11906</Sha>
     </Dependency>
   </ProductDependencies>
   <ToolsetDependencies>
@@ -47,9 +47,9 @@
       <Uri>https://github.com/dotnet/runtime</Uri>
       <Sha>a64420c79cb63c485138f4f1352b8730f27d7b19</Sha>
     </Dependency>
-    <Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="8.0.0-alpha.1.23163.2">
+    <Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="8.0.0-alpha.1.23159.2">
       <Uri>https://github.com/dotnet/source-build-reference-packages</Uri>
-      <Sha>4a1eff2bfa79b608181eb589982a98ebbec60fc6</Sha>
+      <Sha>a848f811a52d75494029e663cd0a799be11744fe</Sha>
       <SourceBuild RepoName="source-build-reference-packages" ManagedOnly="true" />
     </Dependency>
     <Dependency Name="Microsoft.SourceLink.GitHub" Version="1.2.0-beta-23117-02" CoherentParentDependency="Microsoft.DotNet.Arcade.Sdk">
index 6a09478c35af45866997324d9b2e999eb79ac758..332a2ed307b0dd196ac87ff390476bf3fbc19a12 100644 (file)
@@ -16,7 +16,7 @@
   </PropertyGroup>
   <PropertyGroup>
     <!-- Latest symstore version updated by darc -->
-    <MicrosoftSymbolStoreVersion>1.0.416301</MicrosoftSymbolStoreVersion>
+    <MicrosoftSymbolStoreVersion>1.0.415602</MicrosoftSymbolStoreVersion>
     <!-- Latest shared runtime version updated by darc -->
     <VSRedistCommonNetCoreSharedFrameworkx6480Version>8.0.0-preview.3.23155.1</VSRedistCommonNetCoreSharedFrameworkx6480Version>
     <MicrosoftNETCoreAppRuntimewinx64Version>8.0.0-preview.3.23155.1</MicrosoftNETCoreAppRuntimewinx64Version>
@@ -45,7 +45,7 @@
     <SystemReflectionMetadataVersion>5.0.0</SystemReflectionMetadataVersion>
     <!-- Other libs -->
     <MicrosoftBclAsyncInterfacesVersion>1.1.0</MicrosoftBclAsyncInterfacesVersion>
-    <MicrosoftDiagnosticsRuntimeVersion>3.0.0-beta.23163.4</MicrosoftDiagnosticsRuntimeVersion>
+    <MicrosoftDiagnosticsRuntimeVersion>3.0.0-beta.23164.1</MicrosoftDiagnosticsRuntimeVersion>
     <MicrosoftDiaSymReaderNativePackageVersion>16.9.0-beta1.21055.5</MicrosoftDiaSymReaderNativePackageVersion>
     <MicrosoftDiagnosticsTracingTraceEventVersion>3.0.7</MicrosoftDiagnosticsTracingTraceEventVersion>
     <!-- Use pinned version to avoid picking up latest (which doesn't support netcoreapp3.1) during source-build -->
@@ -67,7 +67,7 @@
     <MicrosoftDotNetRemoteExecutorVersion>7.0.0-beta.22316.2</MicrosoftDotNetRemoteExecutorVersion>
     <cdbsosversion>10.0.18362</cdbsosversion>
     <NewtonSoftJsonVersion>13.0.1</NewtonSoftJsonVersion>
-    <MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>8.0.0-alpha.1.23163.2</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>
+    <MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>8.0.0-alpha.1.23159.2</MicrosoftSourceBuildIntermediatesourcebuildreferencepackagesPackageVersion>
     <MicrosoftSourceLinkGitHubVersion>1.2.0-beta-23117-02</MicrosoftSourceLinkGitHubVersion>
     <!-- Roslyn and analyzers -->
     <!-- Compatibility with VS 16.11/.NET SDK 5.0.4xx -->
index ce5334f7fe12f9b989f5f542ee5570b14f70c6bb..b88ad9328fb050b0f05495c9f9613fe34780fcd4 100644 (file)
@@ -145,7 +145,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                 ulong size = obj.IsValid ? obj.Size : 0;
                 if (!StatOnly)
                 {
-                    objectTable.WriteRow(new DmlDumpObj(obj), new DmlDumpHeapMT(obj.Type?.MethodTable ?? 0), size, obj.IsFree ? "Free" : "");
+                    objectTable.WriteRow(new DmlDumpObj(obj), new DmlDumpHeap(obj.Type?.MethodTable ?? 0), size, obj.IsFree ? "Free" : "");
                 }
 
                 if (Strings)
@@ -252,7 +252,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
                     foreach (var item in statsSorted)
                     {
-                        statsTable.WriteRow(new DmlDumpHeapMT(item.MethodTable), item.Count, item.Size, item.TypeName);
+                        statsTable.WriteRow(new DmlDumpHeap(item.MethodTable), item.Count, item.Size, item.TypeName);
                     }
 
                     Console.WriteLine($"Total {stats.Values.Sum(r => r.Count):n0} objects");
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs
new file mode 100644 (file)
index 0000000..3967c4b
--- /dev/null
@@ -0,0 +1,101 @@
+// 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;
+using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    [Command(Name = "gcwhere", Help = "Displays the location in the GC heap of the specified address.")]
+    public class GCWhereCommand : CommandBase
+    {
+        [ServiceImport]
+        public ClrRuntime Runtime { get; set; }
+
+        [ServiceImport]
+        public IMemoryService MemoryService { get; set; }
+
+        [Argument(Help = "The address on the GC heap to list near objects")]
+        public string Address { get; set; }
+
+        public override void Invoke()
+        {
+            if (!TryParseAddress(Address, out ulong address))
+            {
+                throw new ArgumentException($"Could not parse address: {Address}");
+            }
+
+            // We should only ever find zero or one segments, so the output of the table should only have one entry,
+            // but if for some reason the dac reports multiple matching segments (probably due to a bug or inconsistent
+            // data), then we do want to print all of those out here.
+
+            ClrSegment[] segments = FindSegments(address).OrderBy(seg => seg.SubHeap.Index).ThenBy(seg => seg.Address).ToArray();
+            if (segments.Length == 0)
+            {
+                Console.WriteLine($"Address {address:x} not found in the managed heap.");
+                return;
+            }
+
+            (int, string) RangeFormat = (segments.Max(seg => RangeSizeForSegment(seg)), "");
+            TableOutput output = new(Console, (16, "x"), (4, ""), (16, "x"), (10, ""), RangeFormat, RangeFormat, RangeFormat)
+            {
+                AlignLeft = true,
+            };
+
+            output.WriteRow("Address", "Heap", "Segment", "Generation", "Allocated", "Committed", "Reserved");
+            foreach (ClrSegment segment in segments)
+            {
+                string generation;
+                if (segment.ReservedMemory.Contains(address))
+                {
+                    generation = "reserve";
+                }
+                else if (segment.Kind == GCSegmentKind.Frozen)
+                {
+                    generation = "frozen";
+                }
+                else if (segment.Kind == GCSegmentKind.Pinned)
+                {
+                    generation = "pinned";
+                }
+                else if (segment.Kind == GCSegmentKind.Large)
+                {
+                    generation = "large";
+                }
+                else
+                {
+                    int gen = segment.GetGeneration(address);
+                    generation = gen != -1 ? gen.ToString() : "???";
+                }
+
+                object addressColumn = segment.ObjectRange.Contains(address) ? new DmlListNearObj(address) : address;
+                output.WriteRow(addressColumn, segment.SubHeap.Index, segment.Address, generation, new DmlDumpHeap(FormatRange(segment.ObjectRange), segment.ObjectRange), FormatRange(segment.CommittedMemory), FormatRange(segment.ReservedMemory));
+            }
+        }
+
+        private static string FormatRange(MemoryRange range) => $"{range.Start:x}-{range.End:x}";
+
+        private static int RangeSizeForSegment(ClrSegment segment)
+        {
+            // segment.ObjectRange should always be less length than CommittedMemory
+            if (segment.CommittedMemory.Length > segment.ReservedMemory.Length)
+            {
+                return FormatRange(segment.CommittedMemory).Length;
+            }
+            else
+            {
+                return FormatRange(segment.ReservedMemory).Length;
+            }
+        }
+
+        private IEnumerable<ClrSegment> FindSegments(ulong address)
+        {
+            // ClrHeap.GetSegmentByAddress doesn't search for reserve memory
+            return Runtime.Heap.Segments.Where(seg => seg.ObjectRange.Contains(address) || seg.CommittedMemory.Contains(address) || seg.ReservedMemory.Contains(address));
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs
new file mode 100644 (file)
index 0000000..f0bc905
--- /dev/null
@@ -0,0 +1,365 @@
+// 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.Diagnostics;
+using System.Linq;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    [Command(Name = "listnearobj", Help = "Displays the object preceding and succeeding the specified address.")]
+    public class ListNearObjCommand : CommandBase
+    {
+        [ServiceImport]
+        public ClrRuntime Runtime { get; set; }
+
+        [ServiceImport]
+        public IMemoryService MemoryService { get; set; }
+
+        [Argument(Help = "The address on the GC heap to list near objects")]
+        public string Address { get; set; }
+
+        public override void Invoke()
+        {
+            if (!TryParseAddress(Address, out ulong objAddress))
+            {
+                throw new ArgumentException($"Could not parse address: {Address}");
+            }
+
+            // Align objAddress
+            objAddress &= ~((ulong)MemoryService.PointerSize - 1);
+
+            ClrHeap heap = Runtime.Heap;
+
+            // We'll allow any address within the committed range (but not reserve)
+            ClrSegment[] segments = heap.Segments.Where(seg => seg.ObjectRange.Contains(objAddress) || seg.CommittedMemory.Contains(objAddress)).ToArray();
+
+            if (segments.Length == 0)
+            {
+                // Try again the reserve memory.  We could add this to the query above, but we want to give precedence to
+                // allocated/committed memory if we have memory corruption (or an inconsistent GC state) where the reported
+                // reserve overlaps with some other segment.  This shouldn't happen in practice.
+                heap.Segments.Where(seg => seg.ReservedMemory.Contains(objAddress)).ToArray();
+            }
+
+            if (segments.Length == 0)
+            {
+                Console.WriteLine($"Failed to find the segment of the managed heap where the object {objAddress:x} resides");
+                return;
+            }
+
+            ClrSegment segment = segments[0];
+
+            if (segments.Length > 1)
+            {
+                // I've never seen this happen, but better to report it and continue than to error out:
+                Console.WriteLine($"Found multiple segments where the object {objAddress:x} resides, this is possibly memory corruption.");
+                Console.WriteLine($"Printing values for segment {segment.Address:x}");
+            }
+
+            if (segment.ObjectRange.Length == 0)
+            {
+                Console.WriteLine($"Segment {segment.Address:x} has no objects.");
+                return;
+            }
+
+            // If we might have allocation contexts in the target memory range, expand the pointer size column
+            // so that we can print the allocation context range.
+            MemoryRange[] segAllocContexts = heap.EnumerateAllocationContexts().Where(context => segment.ObjectRange.Contains(context.Start)).ToArray();
+            int pointerColumnWidth = segAllocContexts.Length > 0 ? Math.Max(segAllocContexts.Max(r => FormatRange(r).Length), 16) : 16;
+
+            TableOutput output = new(Console, (-"Expected:".Length, ""), (pointerColumnWidth, "x16"), (20, ""), (0, ""));
+
+            // Get current object, but objAddress may not point to an object.
+            ClrObject curr = heap.GetObject(objAddress);
+
+            bool localConsistency = true;
+            bool foundLastObject = false;
+            ulong expectedNextObject;
+
+            // Previous object
+            ClrObject prev = default;
+            if (objAddress > segment.FirstObjectAddress)
+            {
+                // FindPreviousObjectOnSegment may fail if objAddress is not within ObjectRange
+                if (segment.ObjectRange.End <= objAddress)
+                {
+                    prev = heap.FindPreviousObjectOnSegment(segment.ObjectRange.End - 1, carefully: true);
+                }
+                else
+                {
+                    prev = heap.FindPreviousObjectOnSegment(objAddress, carefully: true);
+                }
+
+                Debug.Assert(prev < objAddress); // also works if there's no previous object, Address == 0
+                localConsistency = VerifyAndPrintObject(output, "Before:", heap, segment, prev) && localConsistency;
+
+                if (prev.IsValid)
+                {
+                    expectedNextObject = Align(prev + prev.Size, segment);
+                }
+                else
+                {
+                    localConsistency = false;
+                    expectedNextObject = heap.FindNextObjectOnSegment(prev, carefully: true);
+                }
+
+                // Check for an allocation context
+                MemoryRange allocContextPlusGap = PrintGapIfExists(output, segment, segAllocContexts, new(prev, expectedNextObject));
+                if (allocContextPlusGap.End != 0)
+                {
+                    Debug.Assert(expectedNextObject < allocContextPlusGap.End);
+                    expectedNextObject = allocContextPlusGap.End;
+                }
+
+                if (allocContextPlusGap.Contains(objAddress) && curr.IsValid)
+                {
+                    // The address we were given lives in an allocation context AND it has a valid method table.
+                    // It's likely that this is just an inconsistent/transitional state of the heap.  IE the GC
+                    // has started to allocate objAddress and we got a stale alloc context.
+                    Console.WriteLine($"Address {objAddress:x} has a valid method table inside of an allocation context.");
+                }
+
+                // Is prev the end of the segment?
+                CheckEndOfSegment(segment, expectedNextObject, prev, ref localConsistency, ref foundLastObject);
+            }
+            else if (objAddress < segment.FirstObjectAddress)
+            {
+                Console.WriteLine($"Address {objAddress:x} is before the first object on the segment.");
+                expectedNextObject = segment.FirstObjectAddress;
+            }
+            else
+            {
+                Console.WriteLine($"Address {objAddress:x} is the first object on the segment.");
+                expectedNextObject = segment.FirstObjectAddress;
+            }
+
+            if (!foundLastObject)
+            {
+                // Very odd case that shouldn't happen often:
+                if (expectedNextObject < objAddress && segment.ObjectRange.Contains(expectedNextObject))
+                {
+                    // If we are here, then seg.FindPreviousObjectOnSegment(objAddress) skipped expectedNextObject,
+                    // probably due to corruption.
+                    localConsistency = false;
+
+                    ClrObject expected = heap.GetObject(expectedNextObject);
+                    VerifyAndPrintObject(output, "Expected:", heap, segment, expected);
+                    MemoryRange allocContextPlusGap = PrintGapIfExists(output, segment, segAllocContexts, new(expectedNextObject, objAddress));
+
+                    if (allocContextPlusGap.End != 0)
+                    {
+                        // Whew, we found an allocation context.  We know where to start again:
+                        Debug.Assert(expectedNextObject < allocContextPlusGap.End);
+                        expectedNextObject = allocContextPlusGap.End;
+                    }
+                    else
+                    {
+                        // We don't know where to start next.  If curr is a valid object, use that, if not, try to
+                        // move past "expected", if that doesn't work...give up.
+                        if (curr.IsValid)
+                        {
+                            expectedNextObject = curr;
+                        }
+                        else
+                        {
+                            ClrObject maybeNextObject = heap.FindNextObjectOnSegment(curr + 1, carefully: true);
+                            if (maybeNextObject.IsValid)
+                            {
+                                expected = maybeNextObject;
+                            }
+                            else
+                            {
+                                // Well we can't walk past expected, so this is the end
+                                expectedNextObject = segment.ObjectRange.End;
+                            }
+                        }
+                    }
+
+                    // Is expected the end of the segment?
+                    CheckEndOfSegment(segment, expectedNextObject, expected, ref localConsistency, ref foundLastObject);
+                }
+            }
+
+            // No matter what, print curr if it's valid
+            bool needToPrintGapForCurrent = false;
+            if (curr.IsValid)
+            {
+                if (segment.ObjectRange.Contains(curr))
+                {
+                    localConsistency = VerifyAndPrintObject(output, "Current:", heap, segment, curr) && localConsistency;
+
+                    // If curr is valid, we need to print and skip the allocation context
+                    expectedNextObject = Align(curr + curr.Size, segment);
+                    MemoryRange allocContextPlusGap = PrintGapIfExists(output, segment, segAllocContexts, new(curr, expectedNextObject));
+                    if (allocContextPlusGap.End != 0)
+                    {
+                        Debug.Assert(expectedNextObject <= allocContextPlusGap.End);
+                        expectedNextObject = allocContextPlusGap.End;
+                    }
+
+                    // Is expected the end of the segment?
+                    CheckEndOfSegment(segment, expectedNextObject, curr, ref localConsistency, ref foundLastObject);
+                }
+                else
+                {
+                    // If this value lives outside of the object range, it doesn't affect local consistency.  If
+                    // we are here, then we are likely looking at a recently collected object on a compacted segment
+                    // which hasn't been zeroed yet.
+                    Console.WriteLineError($"Object {objAddress:x} is not in the allocated range of the segment, it may have been collected but not zeroed.");
+                    VerifyAndPrintObject(output, "Current:", heap, segment, curr);
+
+                    foundLastObject = true;
+                }
+            }
+            else if (expectedNextObject == curr)
+            {
+                // The objAddress isn't a valid object but it's exactly where one should be
+                localConsistency = VerifyAndPrintObject(output, "Current:", heap, segment, curr) && localConsistency;
+
+                // Since curr is invalid, we won't know the size of the object to check for an allocation context.
+                // In this case, we'll check again if we found a valid next object to print the gap.
+                needToPrintGapForCurrent = true;
+            }
+
+            if (!foundLastObject)
+            {
+                // Determine "Next:" object
+                ClrObject next = heap.FindNextObjectOnSegment(objAddress, carefully: true);
+                if (next.IsValid)
+                {
+                    if (needToPrintGapForCurrent)
+                    {
+                        PrintGapIfExists(output, segment, segAllocContexts, new(curr, next));
+                    }
+
+                    localConsistency = VerifyAndPrintObject(output, "Next:", heap, segment, next) && localConsistency;
+                    if (next != expectedNextObject)
+                    {
+                        localConsistency = false;
+                        Console.WriteLine($"Expected to find next object at {expectedNextObject:x}, instead found it at {next.Address:x}.");
+                    }
+
+                    // Is expected the end of the segment?
+                    CheckEndOfSegment(segment, expectedNextObject, curr, ref localConsistency, ref foundLastObject);
+                }
+                else
+                {
+                    localConsistency = false;
+                    Console.WriteLine($"Could not find object after {objAddress:x}");
+                }
+            }
+
+            if (localConsistency)
+            {
+                Console.WriteLine("Heap local consistency confirmed.");
+            }
+            else
+            {
+                Console.WriteLine("Heap local consistency not confirmed.");
+            }
+        }
+
+        private void CheckEndOfSegment(ClrSegment segment, ulong expectedNextObject, ulong prevObjectAddress, ref bool localConsistency, ref bool foundLastObject)
+        {
+            if (!segment.ObjectRange.Contains(expectedNextObject) && !foundLastObject)
+            {
+                Console.WriteLineError($"{prevObjectAddress:x} is the last object on the segment");
+                if (expectedNextObject != segment.ObjectRange.End)
+                {
+                    Console.WriteLine($"Error: Expected allocated end at {expectedNextObject:x}, but instead was at {segment.ObjectRange.End:x}");
+                    localConsistency = false;
+                }
+
+                foundLastObject = true;
+            }
+        }
+
+        private MemoryRange PrintGapIfExists(TableOutput output, ClrSegment segment, MemoryRange[] segAllocContexts, MemoryRange objectDistance)
+        {
+            // Print information about allocation context gaps between objects
+            MemoryRange range = segAllocContexts.FirstOrDefault(ctx => objectDistance.Overlaps(ctx) || ctx.Contains(objectDistance.End));
+            if (range.Start != 0)
+            {
+                output.WriteRow("Gap:", FormatRange(range), FormatSize(range.Length), "GC Allocation Context (expected gap in the heap)");
+            }
+
+            // Return the region of memory that does not contain objects.  CLR stores allocation contexts with an ending
+            // that's min_object_size away from the next valid object.  We want to display the alloc_context as CLR sees it,
+            // but we also need to know the invalid memory range to be sure we don't display a bad error message.
+            if (range.End == 0)
+            {
+                return default;
+            }
+
+            uint minObjectSize = (uint)MemoryService.PointerSize * 3;
+            return new(range.Start, range.End + Align(minObjectSize, segment));
+        }
+
+        private static string FormatRange(MemoryRange range) => $"{range.Start:x}-{range.End:x}";
+
+        private ulong Align(ulong size, ClrSegment seg)
+        {
+            ulong AlignConst;
+            ulong AlignLargeConst = 7;
+
+            if (MemoryService.PointerSize == 4)
+            {
+                AlignConst = 3;
+            }
+            else
+            {
+                AlignConst = 7;
+            }
+
+            if (seg.Kind is GCSegmentKind.Large or GCSegmentKind.Pinned)
+            {
+                return (size + AlignLargeConst) & ~AlignLargeConst;
+            }
+
+            return (size + AlignConst) & ~AlignConst;
+        }
+
+        private bool VerifyAndPrintObject(TableOutput output, string which, ClrHeap heap, ClrSegment segment, ClrObject obj)
+        {
+            bool isObjectValid = !heap.IsObjectCorrupted(obj, out ObjectCorruption corruption) && obj.IsValid;
+
+            // Here, isCorrupted may still be true, but it might not interfere with getting the type of the object.
+            // Since we know the information, we will print that out.
+            string typeName = obj.Type?.Name ?? GetErrorTypeName(obj);
+
+            // ClrObject.Size is not available if IsValid returns false
+            string size = FormatSize(obj.IsValid ? obj.Size : 0);
+            if (corruption is null)
+            {
+                output.WriteRow(which, new DmlDumpObj(obj), size, typeName);
+            }
+            else
+            {
+                output.WriteRow(which, new DmlListNearObj(obj), size, typeName);
+                Console.Write($"Error Detected: {VerifyHeapCommand.GetObjectCorruptionMessage(MemoryService, heap, corruption)} ");
+                Console.WriteDmlExec("[verify heap]", $"!verifyheap -s {segment.Address:X}");
+                Console.WriteLine();
+            }
+
+            return isObjectValid;
+        }
+
+        private static string FormatSize(ulong size) => size > 0 ? $"{size:n0} (0x{size:x})" : "";
+
+        private string GetErrorTypeName(ClrObject obj)
+        {
+            if (!MemoryService.ReadPointer(obj.Address, out _))
+            {
+                return $"[error reading mt at: {obj.Address:x}]";
+            }
+            else
+            {
+                return $"Unknown";
+            }
+        }
+    }
+}
index 51806de1817147637f27891f247fe87e7a6c9fe5..6c3d3662de1bd5d8be2caaf9153f5f4da7811a61 100644 (file)
@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
 {
@@ -240,12 +241,40 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             }
         }
 
-        public sealed class DmlDumpHeapMT : DmlExec
+        public sealed class DmlListNearObj : DmlExec
         {
-            public DmlDumpHeapMT(ulong methodTable)
+            public DmlListNearObj(ulong address)
+                : base(address, address != 0 ? $"!sos listnearobj {address:x}" : "")
+            {
+            }
+        }
+
+        public sealed class DmlVerifyObj : DmlExec
+        {
+            public DmlVerifyObj(ulong address)
+                : base(address, address != 0 ? $"!verifyobj /d {address:x}" : "")
+            {
+            }
+        }
+
+        public sealed class DmlDumpHeap : DmlExec
+        {
+            public DmlDumpHeap(string text, MemoryRange range)
+                : base(text, $"!dumpheap {range.Start:x} {range.End:x}")
+            {
+            }
+
+            public DmlDumpHeap(ulong methodTable)
                 : base (methodTable, methodTable != 0 ? $"!dumpheap -mt {methodTable:x}" : "")
             {
+            }
+        }
 
+        public sealed class DmlVerifyHeap : DmlExec
+        {
+            public DmlVerifyHeap(string text, ClrSegment what)
+                : base(text, $"!verifyheap -segment {what.Address}")
+            {
             }
         }
     }
index 07e2bcffd8246f22d22b8fab38e49d906ec6af90..95335a6e41e5757b209df547297dd1acb08414d8 100644 (file)
@@ -140,6 +140,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         }
 
         private void WriteError(ref TableOutput output, ClrHeap heap, ObjectCorruption corruption)
+        {
+            string message = GetObjectCorruptionMessage(MemoryService, heap, corruption);
+            WriteRow(ref output, heap, corruption, message);
+        }
+
+        internal static string GetObjectCorruptionMessage(IMemoryService memory, ClrHeap heap, ObjectCorruption corruption)
         {
             ClrObject obj = corruption.Object;
 
@@ -151,26 +157,25 @@ namespace Microsoft.Diagnostics.ExtensionCommands
 
                 // Object failures
                 ObjectCorruptionKind.ObjectTooLarge => $"Object {obj.Address:x} is too large, size={obj.Size:x}, segmentEnd: {ValueWithError(heap.GetSegmentByAddress(obj)?.End)}",
-                ObjectCorruptionKind.InvalidMethodTable => $"Object {obj.Address:x} has an invalid method table {ReadPointerWithError(obj):x}",
+                ObjectCorruptionKind.InvalidMethodTable => $"Object {obj.Address:x} has an invalid method table {ReadPointerWithError(memory, obj):x}",
                 ObjectCorruptionKind.InvalidThinlock => $"Object {obj.Address:x} has an invalid thin lock",
                 ObjectCorruptionKind.SyncBlockMismatch => GetSyncBlockFailureMessage(corruption),
                 ObjectCorruptionKind.SyncBlockZero => GetSyncBlockFailureMessage(corruption),
 
                 // Object reference failures
                 ObjectCorruptionKind.ObjectReferenceNotPointerAligned => $"Object {obj.Address:x} has an unaligned member at {corruption.Offset:x}: is not pointer aligned",
-                ObjectCorruptionKind.InvalidObjectReference => $"Object {obj.Address:x} has a bad member at offset {corruption.Offset:x}: {ReadPointerWithError(obj + (uint)corruption.Offset)}",
-                ObjectCorruptionKind.FreeObjectReference => $"Object {obj.Address:x} contains free object at offset {corruption.Offset:x}: {ReadPointerWithError(obj + (uint)corruption.Offset)}",
+                ObjectCorruptionKind.InvalidObjectReference => $"Object {obj.Address:x} has a bad member at offset {corruption.Offset:x}: {ReadPointerWithError(memory, obj + (uint)corruption.Offset)}",
+                ObjectCorruptionKind.FreeObjectReference => $"Object {obj.Address:x} contains free object at offset {corruption.Offset:x}: {ReadPointerWithError(memory, obj + (uint)corruption.Offset)}",
 
                 // Memory read failures
-                ObjectCorruptionKind.CouldNotReadObject => $"Could not read object {obj.Address:x} at offset {corruption.Offset:x}: {ReadPointerWithError(obj + (uint)corruption.Offset)}",
+                ObjectCorruptionKind.CouldNotReadObject => $"Could not read object {obj.Address:x} at offset {corruption.Offset:x}: {ReadPointerWithError(memory, obj + (uint)corruption.Offset)}",
                 ObjectCorruptionKind.CouldNotReadMethodTable => $"Could not read method table for Object {obj.Address:x}",
                 ObjectCorruptionKind.CouldNotReadCardTable => $"Could not verify object {obj.Address:x}: could not read card table",
                 ObjectCorruptionKind.CouldNotReadGCDesc => $"Could not verify object {obj.Address:x}: could not read GCDesc",
 
                 _ => ""
             };
-
-            WriteRow(ref output, heap, corruption, message);
+            return message;
         }
 
         private void WriteRow(ref TableOutput output, ClrHeap heap, ObjectCorruption corruption, string message)
@@ -270,9 +275,9 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             return error;
         }
 
-        private string ReadPointerWithError(ulong address)
+        private static string ReadPointerWithError(IMemoryService memory, ulong address)
         {
-            if (MemoryService.ReadPointer(address, out ulong value))
+            if (memory.ReadPointer(address, out ulong value))
             {
                 return value.ToString("x");
             }
index db4955aadb992bbcbf939bdae59507cb3537e856..9436ac3b932418adbc164d729cc52b1d14d0f351 100644 (file)
@@ -40,7 +40,6 @@ namespace SOS.Hosting
     [Command(Name = "gcheapstat",        DefaultOptions = "GCHeapStat",          Help = "Displays various GC heap stats.")]
     [Command(Name = "gcroot",            DefaultOptions = "GCRoot",              Help = "Displays info about references (or roots) to an object at the specified address.")]
     [Command(Name = "gcinfo",            DefaultOptions = "GCInfo",              Help = "Displays JIT GC encoding for a method.")]
-    [Command(Name = "gcwhere",           DefaultOptions = "GCWhere",             Help = "Displays the location in the GC heap of the specified address.")]
     [Command(Name = "histclear",         DefaultOptions = "HistClear",           Help = "Releases any resources used by the family of Hist commands.")]
     [Command(Name = "histinit",          DefaultOptions = "HistInit",            Help = "Initializes the SOS structures from the stress log saved in the debuggee.")]
     [Command(Name = "histobj",           DefaultOptions = "HistObj",             Help = "Examines all stress log relocation records and displays the chain of garbage collection relocations that may have led to  the address passed in as an argument.")]
@@ -48,7 +47,6 @@ namespace SOS.Hosting
     [Command(Name = "histroot",          DefaultOptions = "HistRoot",            Help = "Displays information related to both promotions and relocations of the specified root.")]
     [Command(Name = "histstats",         DefaultOptions = "HistStats",           Help = "Displays stress log stats.")]
     [Command(Name = "ip2md",             DefaultOptions = "IP2MD",               Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")]
-    [Command(Name = "listnearobj",       DefaultOptions = "ListNearObj",         Help = "Displays the object preceding and succeeding the specified address.")]
     [Command(Name = "name2ee",           DefaultOptions = "Name2EE",             Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")]
     [Command(Name = "objsize",           DefaultOptions = "ObjSize",             Help = "Lists the sizes of the all the objects found on managed threads.")]
     [Command(Name = "pathto",            DefaultOptions = "PathTo",              Help = "Displays the GC path from <root> to <target>.")]
index f384c6bfd96e8d4c9b03e3362b0f6dd1c8eaec10..1dfea616de0ad4c37e30229c417d07ac513ecc0b 100644 (file)
@@ -27,8 +27,8 @@ VERIFY:\s+Array:       Rank 1, Number of elements 100, Type Byte\s+
 SOSCOMMAND:ObjSize <PREVPOUT>
 
 SOSCOMMAND:GCWhere <PREVPOUT>
-# we care that the Gen is 4 (POH)
-VERIFY:<HEXVAL>\s+4\s+\d\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+0x<HEXVAL>\s*\(\d+\)
+# we care that the Gen is 'pinned' (POH)
+VERIFY:\s*<HEXVAL>\s+<DECVAL>\s+<HEXVAL>\s+pinned.*
 
 SOSCOMMAND:GCRoot <PREVPOUT>
 VERIFY:.*Thread <HEXVAL>:
index 7b79d82c0979d5489ca84041edc1713cea4ec381..0d83157d4478b301b0cd96a4000757d1cd7096af 100644 (file)
@@ -40,7 +40,7 @@ VERIFY:<HEXVAL>\s<HEXVAL>\s([Gg][Cc]where!\$0_)*GCWhere\s+
 
 SOSCOMMAND:GCWhere <POUT>\w+\s+(<HEXVAL>)\s+([Gg][Cc]where!\$0_)*GCWhere\s+<POUT>
 # we care that the Gen is 0
-VERIFY:<HEXVAL>\s+0\s+\d\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+0x<HEXVAL>\s*\(\d+\)
+VERIFY:\s*<HEXVAL>\s+<DECVAL>\s+<HEXVAL>\s+0.*
 
 SOSCOMMAND:GCRoot <PREVPOUT>
 VERIFY:.*Thread <HEXVAL>:
@@ -110,7 +110,7 @@ ENDIF:CDB
 SOSCOMMAND:DumpStackObjects
 SOSCOMMAND:GCWhere <POUT>\w+\s+(<HEXVAL>)\s+([Gg][Cc]where!\$0_)*GCWhere\s+<POUT>
 # we care that the Gen is 1
-VERIFY:<HEXVAL>\s+1\s+\d\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+0x<HEXVAL>\s*\(\d+\)
+VERIFY:\s*<HEXVAL>\s+<DECVAL>\s+<HEXVAL>\s+1.*
 
 SOSCOMMAND:GCRoot <PREVPOUT>
 VERIFY:.*Thread <HEXVAL>:
@@ -159,7 +159,7 @@ SOSCOMMAND:EEHeap -gc
 SOSCOMMAND:DumpStackObjects
 SOSCOMMAND:GCWhere <POUT>\w+\s+(<HEXVAL>)\s+([Gg][Cc]where!\$0_)*GCWhere\s+<POUT>
 # we care that the Gen is 2
-VERIFY:<HEXVAL>\s+2\s+\d\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+0x<HEXVAL>\s*\(\d+\)
+VERIFY:\s*<HEXVAL>\s+<DECVAL>\s+<HEXVAL>\s+2.*
 
 SOSCOMMAND:GCRoot <PREVPOUT>
 VERIFY:.*Thread <HEXVAL>:
@@ -181,8 +181,8 @@ SOSCOMMAND:EEHeap -gc
 
 SOSCOMMAND:DumpStackObjects
 SOSCOMMAND:GCWhere <POUT>\w+\s+(<HEXVAL>)\s+([Gg][Cc]where!\$0_)*GCWhere\s+<POUT>
-# we care that the Gen is still 2 or 0 on Windows 3.x 
-VERIFY:<HEXVAL>\s+[02]\s+\d\s+<HEXVAL>\s+<HEXVAL>\s+<HEXVAL>\s+0x<HEXVAL>\s*\(\d+\)
+# we care that the Gen is still 2 or 0 on Windows 3.x
+VERIFY:\s*<HEXVAL>\s+<DECVAL>\s+<HEXVAL>\s+[02].*
 
 SOSCOMMAND:GCRoot <PREVPOUT>
 VERIFY:.*Thread <HEXVAL>:
index c3ba651d2bcfbbac93191facce76b5547237d4b5..1342b35d67cb90c36d225717e74b182ffba97973 100644 (file)
@@ -432,442 +432,6 @@ size_t AlignLarge(size_t nbytes)
     return (nbytes + ALIGNCONSTLARGE) & ~ALIGNCONSTLARGE;
 }
 
-/**********************************************************************\
-* Routine Description:                                                 *
-*                                                                      *
-*    Print the gc heap info.                                           *
-*                                                                      *
-\**********************************************************************/
-void GCPrintGenerationInfo(const GCHeapDetails &heap)
-{
-    UINT n;
-    for (n = 0; n <= GetMaxGeneration(); n++)
-    {
-        if (IsInterrupt())
-            return;
-        if (heap.has_regions)
-        {
-            DacpHeapSegmentData segment;
-            DWORD_PTR dwAddrSeg = (DWORD_PTR)heap.generation_table[n].start_segment;
-            if (segment.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK)
-            {
-                ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
-                return;
-            }
-            ExtOut("generation %d starts at 0x%p\n",
-                n, SOS_PTR(SOS_PTR(segment.mem)));
-        }
-        else
-        {
-            ExtOut("generation %d starts at 0x%p\n",
-                n, SOS_PTR(heap.generation_table[n].allocation_start));
-        }
-    }
-
-    // We also need to look at the gen0 alloc context.
-    ExtOut("ephemeral segment allocation context: ");
-    if (heap.generation_table[0].allocContextPtr)
-    {
-        ExtOut("(0x%p, 0x%p)\n",
-            SOS_PTR(heap.generation_table[0].allocContextPtr),
-            SOS_PTR(heap.generation_table[0].allocContextLimit + Align(min_obj_size)));
-    }
-    else
-    {
-        ExtOut("none\n");
-    }
-}
-
-
-void GCPrintSegmentInfo(const GCHeapDetails &heap, DWORD_PTR &total_allocated_size, DWORD_PTR &total_committed_size)
-{
-    DWORD_PTR dwAddrSeg;
-    DacpHeapSegmentData segment;
-    const size_t heap_segment_flags_readonly = 1;
-    UINT max_generation = GetMaxGeneration();
-
-    if (heap.has_regions)
-    {
-        const size_t regions_committed_adjustment = 0x20;
-
-        for (UINT n = 0; n <= max_generation + 1; n++)
-        {
-            bool showing_frozen = (n == max_generation + 1);
-            if (showing_frozen)
-            {
-                ExtOut("Frozen object heap\n");
-                ExtOut("%" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s\n", "segment", "begin", "allocated", "committed", "allocated size", "committed size");
-                dwAddrSeg = (DWORD_PTR)heap.generation_table[max_generation].start_segment;
-            }
-            else
-            {
-                ExtOut("generation %d:\n", n);
-                dwAddrSeg = (DWORD_PTR)heap.generation_table[n].start_segment;
-            }
-            while (dwAddrSeg != 0)
-            {
-                if (IsInterrupt())
-                    return;
-                if (segment.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK)
-                {
-                    ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
-                    return;
-                }
-                
-                CLRDATA_ADDRESS allocated = segment.highAllocMark - segment.mem;
-                CLRDATA_ADDRESS committed = segment.committed - segment.mem + regions_committed_adjustment;
-                bool frozen = segment.flags & heap_segment_flags_readonly;
-                
-                if (frozen != showing_frozen)
-                {
-                    dwAddrSeg = (DWORD_PTR)segment.next;
-                    continue;
-                }
-                ExtOut("%p  %p  %p  %p  0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE"d)  0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n",
-                    SOS_PTR(dwAddrSeg),
-                    SOS_PTR(segment.mem), SOS_PTR(segment.highAllocMark), SOS_PTR(segment.committed),
-                    (ULONG_PTR)allocated,
-                    (ULONG_PTR)allocated,
-                    (ULONG_PTR)committed,
-                    (ULONG_PTR)committed);
-                total_allocated_size += (DWORD_PTR)allocated;
-                total_committed_size += (DWORD_PTR)committed;
-                dwAddrSeg = (DWORD_PTR)segment.next;
-            }
-        }
-    }
-    else
-    {
-        for (UINT n = 0; n < 2; n++)
-        {
-            dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment;
-            bool showing_frozen = (n == 1);
-            if (showing_frozen)
-            {
-                ExtOut("Frozen object heap\n");
-                ExtOut("%" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s\n", "segment", "begin", "allocated", "committed", "allocated size", "committed size");
-            }
-            while (true)
-            {
-                if (IsInterrupt())
-                    return;
-
-                if (segment.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK)
-                {
-                    ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
-                    return;
-                }
-
-                CLRDATA_ADDRESS allocated = segment.highAllocMark - segment.mem;
-                CLRDATA_ADDRESS committed = segment.committed - segment.mem;
-                bool frozen = segment.flags & heap_segment_flags_readonly;
-
-                if (frozen != showing_frozen)
-                {
-                    if (dwAddrSeg == (DWORD_PTR)heap.generation_table[0].start_segment)
-                    {
-                        break;
-                    }
-                    dwAddrSeg = (DWORD_PTR)segment.next;
-                    continue;
-                }
-
-                ExtOut("%p  %p  %p  %p  0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE"d)  0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n",
-                        SOS_PTR(dwAddrSeg),
-                        SOS_PTR(segment.mem), SOS_PTR(segment.allocated), SOS_PTR(segment.committed),
-                        (ULONG_PTR)allocated,
-                        (ULONG_PTR)allocated,
-                        (ULONG_PTR)committed,
-                        (ULONG_PTR)committed);
-                total_allocated_size += (DWORD_PTR)allocated;
-                total_committed_size += (DWORD_PTR)committed;
-                if (dwAddrSeg == (DWORD_PTR)heap.generation_table[0].start_segment)
-                {
-                    break;
-                }
-                dwAddrSeg = (DWORD_PTR)segment.next;
-            }
-        }
-    }
-}
-
-void GCPrintUohHeapSegmentInfo(const GCHeapDetails &heap, UINT generation, DWORD_PTR &total_allocated_size, DWORD_PTR &total_committed_size)
-{
-    DWORD_PTR dwAddrSeg;
-    DacpHeapSegmentData segment;
-    dwAddrSeg = (DWORD_PTR)heap.generation_table[generation].start_segment;
-    const size_t regions_committed_adjustment = 0x20;
-
-    // total_size = 0;
-    while (dwAddrSeg != NULL)
-    {
-        if (IsInterrupt())
-            return;
-        if (segment.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK)
-        {
-            ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg));
-            return;
-        }
-        CLRDATA_ADDRESS allocated = segment.allocated - segment.mem;
-        CLRDATA_ADDRESS committed = segment.committed - segment.mem;
-        if (heap.has_regions)
-        {
-            committed += regions_committed_adjustment;
-        }
-        ExtOut("%p  %p  %p  %p  0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE"d)  0x%" POINTERSIZE_TYPE "x(%" POINTERSIZE_TYPE "d)\n",
-                SOS_PTR(dwAddrSeg),
-                SOS_PTR(segment.mem),
-                SOS_PTR(segment.allocated),
-                SOS_PTR(segment.committed),
-                (ULONG_PTR)allocated,
-                (ULONG_PTR)allocated,
-                (ULONG_PTR)committed,
-                (ULONG_PTR)committed);
-        total_allocated_size += (DWORD_PTR)allocated;
-        total_committed_size += (DWORD_PTR)committed;
-        dwAddrSeg = (DWORD_PTR)segment.next;
-    }
-}
-
-void GCHeapInfo(const GCHeapDetails &heap, DWORD_PTR &total_allocated_size, DWORD_PTR &total_committed_size)
-{
-    if (!heap.has_regions)
-    {
-        GCPrintGenerationInfo(heap);
-    }
-    ExtOut("Small object heap\n");
-    ExtOut("%" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s\n", "segment", "begin", "allocated", "committed", "allocated size", "committed size");
-    GCPrintSegmentInfo(heap, total_allocated_size, total_committed_size);
-
-    if (heap.has_regions)
-    {
-        ExtOut("Large object heap\n");
-    }
-    else
-    {
-        ExtOut("Large object heap starts at 0x%p\n", SOS_PTR(heap.generation_table[GetMaxGeneration() + 1].allocation_start));
-    }
-    ExtOut("%" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s\n", "segment", "begin", "allocated", "committed", "allocated size", "committed size");
-    GCPrintUohHeapSegmentInfo(heap, GetMaxGeneration() + 1, total_allocated_size, total_committed_size);
-
-    if (heap.has_poh)
-    {
-        if (heap.has_regions)
-        {
-            ExtOut("Pinned object heap\n");
-        }
-        else
-        {
-            ExtOut("Pinned object heap starts at 0x%p\n", SOS_PTR(heap.generation_table[GetMaxGeneration() + 2].allocation_start));
-        }
-        ExtOut("%" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s  %" POINTERSIZE "s\n", "segment", "begin", "allocated", "committed", "allocated size", "committed size");
-        GCPrintUohHeapSegmentInfo(heap, GetMaxGeneration() + 2, total_allocated_size, total_committed_size);
-    }
-}
-
-BOOL GCObjInGeneration(TADDR taddrObj, const GCHeapDetails &heap,
-    const TADDR_SEGINFO& /*seg*/, int& gen, TADDR_RANGE& allocCtx)
-{
-    // we will not get here in case of regions as our caller in 
-    // GCObjInSegment already takes care of this
-    assert(!heap.has_regions);
-
-    gen = -1;
-    for (UINT n = 0; n <= GetMaxGeneration(); n ++)
-    {
-        if (taddrObj >= TO_TADDR(heap.generation_table[n].allocation_start))
-        {
-            gen = n;
-            break;
-        }
-    }
-
-    // We also need to look at the gen0 alloc context.
-    if (heap.generation_table[0].allocContextPtr
-        && taddrObj >= TO_TADDR(heap.generation_table[0].allocContextPtr)
-        && taddrObj < TO_TADDR(heap.generation_table[0].allocContextLimit) + Align(min_obj_size))
-    {
-        gen = 0;
-        allocCtx.start = (TADDR)heap.generation_table[0].allocContextPtr;
-        allocCtx.end   = (TADDR)heap.generation_table[0].allocContextLimit;
-    }
-    else
-    {
-        allocCtx.start = allocCtx.end = 0;
-    }
-    return (gen != -1);
-}
-
-
-BOOL GCObjInSegment(TADDR taddrObj, const GCHeapDetails &heap,
-    TADDR_SEGINFO& rngSeg, int& gen, TADDR_RANGE& allocCtx)
-{
-    TADDR taddrSeg;
-    DacpHeapSegmentData dacpSeg;
-
-    if (heap.has_regions)
-    {
-        // in this case, each generation has its own list
-        for (unsigned gen_num = 0; gen_num <= GetMaxGeneration(); gen_num++)
-        {
-            taddrSeg = (TADDR)heap.generation_table[gen_num].start_segment;
-
-            while (taddrSeg != NULL)
-            {
-                if (IsInterrupt())
-                    return FALSE;
-                if (dacpSeg.Request(g_sos, taddrSeg, heap.original_heap_details) != S_OK)
-                {
-                    ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
-                    return FALSE;
-                }
-                if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < dacpSeg.highAllocMark)
-                {
-                    rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
-                    rngSeg.start = (TADDR)dacpSeg.mem;
-                    rngSeg.end = (TADDR)dacpSeg.highAllocMark;
-                    gen = gen_num;
-                    return TRUE;
-                }
-                taddrSeg = (TADDR)dacpSeg.next;
-            }
-        }
-        return FALSE;
-    }
-    else
-    {
-        taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()].start_segment;
-
-        while (taddrSeg != (TADDR)heap.generation_table[0].start_segment)
-        {
-            if (IsInterrupt())
-                return FALSE;
-            if (dacpSeg.Request(g_sos, taddrSeg, heap.original_heap_details) != S_OK)
-            {
-                ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
-                return FALSE;
-            }
-            if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(dacpSeg.allocated))
-            {
-                rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
-                rngSeg.start   = (TADDR)dacpSeg.mem;
-                rngSeg.end     = (TADDR)dacpSeg.allocated;
-                gen = 2;
-                allocCtx.start = allocCtx.end = 0;
-                return TRUE;
-            }
-            taddrSeg = (TADDR)dacpSeg.next;
-        }
-
-        // the ephemeral segment
-        if (dacpSeg.Request(g_sos, taddrSeg, heap.original_heap_details) != S_OK)
-        {
-            ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
-            return FALSE;
-        }
-
-        if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(heap.alloc_allocated))
-        {
-            if (GCObjInGeneration(taddrObj, heap, rngSeg, gen, allocCtx))
-            {
-                rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
-                rngSeg.start   = (TADDR)dacpSeg.mem;
-                rngSeg.end     = (TADDR)heap.alloc_allocated;
-                return TRUE;
-            }
-        }
-    }
-
-    return FALSE;
-}
-
-BOOL GCObjInLargeSegment(TADDR taddrObj, const GCHeapDetails &heap, TADDR_SEGINFO& rngSeg)
-{
-    TADDR taddrSeg;
-    DacpHeapSegmentData dacpSeg;
-    taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration()+1].start_segment;
-
-    while (taddrSeg != NULL)
-    {
-        if (IsInterrupt())
-            return FALSE;
-        if (dacpSeg.Request(g_sos, taddrSeg, heap.original_heap_details) != S_OK)
-        {
-            ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
-            return FALSE;
-        }
-        if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(dacpSeg.allocated))
-        {
-            rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
-            rngSeg.start   = (TADDR)dacpSeg.mem;
-            rngSeg.end     = (TADDR)dacpSeg.allocated;
-            return TRUE;
-        }
-        taddrSeg = (TADDR)dacpSeg.next;
-    }
-    return FALSE;
-}
-
-BOOL GCObjInPinnedObjectSegment(TADDR taddrObj, const GCHeapDetails &heap, TADDR_SEGINFO& rngSeg)
-{
-    if (!heap.has_poh)
-    {
-        return FALSE;
-    }
-
-    TADDR taddrSeg;
-    DacpHeapSegmentData dacpSeg;
-    taddrSeg = (TADDR)heap.generation_table[GetMaxGeneration() + 2].start_segment;
-
-    while (taddrSeg != NULL)
-    {
-        if (IsInterrupt())
-            return FALSE;
-        if (dacpSeg.Request(g_sos, taddrSeg, heap.original_heap_details) != S_OK)
-        {
-            ExtOut("Error requesting heap segment %p\n", SOS_PTR(taddrSeg));
-            return FALSE;
-        }
-        if (taddrObj >= TO_TADDR(dacpSeg.mem) && taddrObj < TO_TADDR(dacpSeg.allocated))
-        {
-            rngSeg.segAddr = (TADDR)dacpSeg.segmentAddr;
-            rngSeg.start   = (TADDR)dacpSeg.mem;
-            rngSeg.end     = (TADDR)dacpSeg.allocated;
-            return TRUE;
-        }
-        taddrSeg = (TADDR)dacpSeg.next;
-    }
-    return FALSE;
-}
-
-BOOL GCObjInHeap(TADDR taddrObj, const GCHeapDetails &heap, TADDR_SEGINFO& rngSeg,
-    int& gen, TADDR_RANGE& allocCtx, BOOL &bLarge)
-{
-    bLarge = FALSE;
-
-    if (GCObjInSegment(taddrObj, heap, rngSeg, gen, allocCtx))
-    {
-        return TRUE;
-    }
-
-    if (GCObjInLargeSegment(taddrObj, heap, rngSeg))
-    {
-        bLarge = TRUE;
-        gen = GetMaxGeneration() + 1;
-        allocCtx.start = allocCtx.end = 0;
-        return TRUE;
-    }
-
-    if (GCObjInPinnedObjectSegment(taddrObj, heap, rngSeg))
-    {
-        gen = GetMaxGeneration() + 2;
-        allocCtx.start = allocCtx.end = 0;
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
 // this function updates genUsage to reflect statistics from the range defined by [start, end)
 void GCGenUsageStats(TADDR start, TADDR alloc_end, TADDR commit_end, const std::unordered_set<TADDR> &liveObjs,
     const GCHeapDetails &heap, BOOL bLarge, BOOL bPinned, const AllocInfo *pAllocInfo, GenUsageStat *genUsage)
index 463e17393cdbb7b3ee3197e0ddf157c19d630a6c..ed0969b3d5a944fadc2c3c78be032ac69442072d 100644 (file)
@@ -4110,187 +4110,12 @@ Exit:
     return Status;
 }
 
-void LNODisplayOutput(LPCWSTR tag, TADDR pMT, TADDR currentObj, size_t size)
-{
-    sos::Object obj(currentObj, pMT);
-    DMLOut("%S %s %12d (0x%x)\t%S\n", tag, DMLObject(currentObj), size, size, obj.GetTypeName());
-}
-
 DECLARE_API(ListNearObj)
 {
-    INIT_API();
+    INIT_API_EXT();
     MINIDUMP_NOT_SUPPORTED();
 
-    TADDR taddrArg = 0;
-    TADDR taddrObj = 0;
-    BOOL dml = FALSE;
-    CMDOption option[] =
-    {   // name, vptr, type, hasValue
-        {"/d", &dml, COBOOL, FALSE},
-    };
-    CMDValue arg[] =
-    {
-        // vptr, type
-        {&taddrArg, COHEX}
-    };
-    size_t nArg;
-    if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg) || nArg != 1)
-    {
-        ExtOut("Usage: !ListNearObj <obj_address>\n");
-        return Status;
-    }
-
-    EnableDMLHolder dmlHolder(dml);
-
-    if (!g_snapshot.Build())
-    {
-        ExtOut("Unable to build snapshot of the garbage collector state\n");
-        return Status;
-    }
-
-    taddrObj = Align(taddrArg);
-
-    GCHeapDetails *heap = g_snapshot.GetHeap(taddrArg);
-    if (heap == NULL)
-    {
-        ExtOut("Address %p does not lie in the managed heap\n", SOS_PTR(taddrObj));
-        return Status;
-    }
-
-    TADDR_SEGINFO trngSeg  = {0, 0, 0};
-    TADDR_RANGE   allocCtx = {0, 0};
-    BOOL          bLarge;
-    int           gen;
-    if (!GCObjInHeap(taddrObj, *heap, trngSeg, gen, allocCtx, bLarge))
-    {
-        ExtOut("Failed to find the segment of the managed heap where the object %p resides\n",
-            SOS_PTR(taddrObj));
-        return Status;
-    }
-
-    TADDR  objMT = NULL;
-    size_t objSize = 0;
-    BOOL   bObj    = FALSE;
-    TADDR  taddrCur;
-    TADDR  curMT   = 0;
-    size_t curSize = 0;
-    BOOL   bCur    = FALSE;
-    TADDR  taddrNxt;
-    TADDR  nxtMT   = 0;
-    size_t nxtSize = 0;
-    BOOL   bNxt    = FALSE;
-    BOOL   bContainsPointers;
-
-    std::vector<TADDR> candidate;
-    candidate.reserve(10);
-
-    // since we'll be reading back I'll prime the read cache to a buffer before the current address
-    MOVE(taddrCur, _max(trngSeg.start, taddrObj-DT_OS_PAGE_SIZE));
-
-    // ===== Look for a good candidate preceeding taddrObj
-
-    for (taddrCur = taddrObj - sizeof(TADDR); taddrCur >= trngSeg.start; taddrCur -= sizeof(TADDR))
-    {
-        // currently we don't pay attention to allocation contexts.  if this
-        // proves to be an issue we need to reconsider the code below
-        if (SUCCEEDED(GetMTOfObject(taddrCur, &curMT)) &&
-            GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers))
-        {
-            // remember this as one of the possible "good" objects preceeding taddrObj
-            candidate.push_back(taddrCur);
-
-            std::vector<TADDR>::iterator it =
-                std::find(candidate.begin(), candidate.end(), taddrCur+curSize);
-            if (it != candidate.end())
-            {
-                // We found a chain of two objects preceeding taddrObj.  We'll
-                // trust this is a good indication that the two objects are valid.
-                // What is not valid is possibly the object following the second
-                // one...
-                taddrCur = *it;
-                GetMTOfObject(taddrCur, &curMT);
-                GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers);
-                bCur = TRUE;
-                break;
-            }
-        }
-    }
-
-    if (!bCur && !candidate.empty())
-    {
-        // pick the closest object to taddrObj
-        taddrCur = *(candidate.begin());
-        GetMTOfObject(taddrCur, &curMT);
-        GetSizeEfficient(taddrCur, curMT, bLarge, curSize, bContainsPointers);
-        // we have a candidate, even if not confirmed
-        bCur = TRUE;
-    }
-
-    taddrNxt = taddrObj;
-    if (taddrArg == taddrObj)
-    {
-        taddrNxt += sizeof(TADDR);
-    }
-
-    // ===== Now look at taddrObj
-    if (taddrObj == taddrArg)
-    {
-        // only look at taddrObj if it's the same as what user passed in, meaning it's aligned.
-        if (SUCCEEDED(GetMTOfObject(taddrObj, &objMT)) &&
-            GetSizeEfficient(taddrObj, objMT, bLarge, objSize, bContainsPointers))
-        {
-            bObj = TRUE;
-            taddrNxt = taddrObj+objSize;
-        }
-    }
-
-    if ((taddrCur + curSize > taddrArg) && taddrCur + curSize < trngSeg.end)
-    {
-        if (SUCCEEDED(GetMTOfObject(taddrCur + curSize, &nxtMT)) &&
-            GetSizeEfficient(taddrObj, objMT, bLarge, objSize, bContainsPointers))
-        {
-            taddrNxt = taddrCur+curSize;
-        }
-    }
-
-    // ===== And finally move on to elements following taddrObj
-
-    for (; taddrNxt < trngSeg.end; taddrNxt += sizeof(TADDR))
-    {
-        if (SUCCEEDED(GetMTOfObject(taddrNxt, &nxtMT)) &&
-            GetSizeEfficient(taddrNxt, nxtMT, bLarge, nxtSize, bContainsPointers))
-        {
-            bNxt = TRUE;
-            break;
-        }
-    }
-
-    if (bCur)
-        LNODisplayOutput(W("Before: "), curMT, taddrCur, curSize);
-    else
-        ExtOut("Before: couldn't find any object between %#p and %#p\n",
-            SOS_PTR(trngSeg.start), SOS_PTR(taddrArg));
-
-    if (bObj)
-        LNODisplayOutput(W("Current:"), objMT, taddrObj, objSize);
-
-    if (bNxt)
-        LNODisplayOutput(W("After:  "), nxtMT, taddrNxt, nxtSize);
-    else
-        ExtOut("After:  couldn't find any object between %#p and %#p\n",
-            SOS_PTR(taddrArg), SOS_PTR(trngSeg.end));
-
-    if (bCur && bNxt &&
-        (((taddrCur + curSize == taddrObj) && (taddrObj + objSize == taddrNxt)) || (taddrCur + curSize == taddrNxt)))
-    {
-        ExtOut("Heap local consistency confirmed.\n");
-    }
-    else
-    {
-        ExtOut("Heap local consistency not confirmed.\n");
-    }
-
-    return Status;
+    return ExecuteCommand("listnearobj", args);
 }
 
 DECLARE_API(GCHeapStat)
@@ -10336,116 +10161,10 @@ DECLARE_API(GCRoot)
 
 DECLARE_API(GCWhere)
 {
-    INIT_API();
+    INIT_API_EXT();
     MINIDUMP_NOT_SUPPORTED();
 
-    BOOL dml = FALSE;
-    BOOL bGetBrick;
-    BOOL bGetCard;
-    TADDR taddrObj = 0;
-    size_t nArg;
-
-    CMDOption option[] =
-    {   // name, vptr, type, hasValue
-        {"-brick", &bGetBrick, COBOOL, FALSE},
-        {"-card", &bGetCard, COBOOL, FALSE},
-        {"/d", &dml, COBOOL, FALSE},
-    };
-    CMDValue arg[] =
-    {   // vptr, type
-        {&taddrObj, COHEX}
-    };
-    if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
-    {
-        return Status;
-    }
-
-    EnableDMLHolder dmlHolder(dml);
-    // Obtain allocation context for each managed thread.
-    AllocInfo allocInfo;
-    allocInfo.Init();
-
-    TADDR_SEGINFO trngSeg  = { 0, 0, 0 };
-    TADDR_RANGE   allocCtx = { 0, 0 };
-    int   gen = -1;
-    BOOL  bLarge = FALSE;
-    BOOL  bFound = FALSE;
-
-    size_t size = 0;
-    if (sos::IsObject(taddrObj))
-    {
-        TADDR taddrMT;
-        BOOL  bContainsPointers;
-        if(FAILED(GetMTOfObject(taddrObj, &taddrMT)) ||
-           !GetSizeEfficient(taddrObj, taddrMT, FALSE, size, bContainsPointers))
-        {
-            ExtWarn("Couldn't get size for object %#p: possible heap corruption.\n",
-                SOS_PTR(taddrObj));
-        }
-    }
-
-    DWORD heapIdx = 0;
-    if (!IsServerBuild())
-    {
-        DacpGcHeapDetails heapDetails;
-        if (heapDetails.Request(g_sos) != S_OK)
-        {
-            ExtOut("Error requesting gc heap details\n");
-            return Status;
-        }
-
-        bFound = GCObjInHeap(taddrObj, heapDetails, trngSeg, gen, allocCtx, bLarge);
-    }
-    else
-    {
-        DacpGcHeapData gcheap;
-        if (gcheap.Request(g_sos) != S_OK)
-        {
-            ExtOut("Error requesting GC Heap data\n");
-            return Status;
-        }
-
-        DWORD dwAllocSize;
-        DWORD dwNHeaps = gcheap.HeapCount;
-        if (!ClrSafeInt<DWORD>::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 (heapIdx = 0; heapIdx < dwNHeaps && !bFound; heapIdx++)
-        {
-            DacpGcHeapDetails dacHeapDetails;
-            if (dacHeapDetails.Request(g_sos, heapAddrs[heapIdx]) != S_OK)
-            {
-                ExtOut("Error requesting details\n");
-                return Status;
-            }
-
-            GCHeapDetails heapDetails(dacHeapDetails, heapAddrs[heapIdx]);
-            bFound = GCObjInHeap(taddrObj, heapDetails, trngSeg, gen, allocCtx, bLarge);
-        }
-    }
-
-    if (!bFound)
-    {
-        ExtOut("Address %#p not found in the managed heap.\n", SOS_PTR(taddrObj));
-    }
-    else
-    {
-        ExtOut("Address " WIN64_8SPACES " Gen Heap segment " WIN64_8SPACES " begin   " WIN64_8SPACES " allocated" WIN64_8SPACES " size\n");
-        ExtOut("%p   %d     %2d     %p   %p   %p    0x%x(%d)\n",
-            SOS_PTR(taddrObj), gen, heapIdx, SOS_PTR(trngSeg.segAddr), SOS_PTR(trngSeg.start), SOS_PTR(trngSeg.end), size, size);
-    }
-
-    return Status;
+    return ExecuteCommand("gcwhere", args);
 }
 
 DECLARE_API(FindRoots)
index 07c78d237b21687815cfa72a0ec9b32fa1194ce9..fb40002004b73b854f05ba08a990e8f351a6c1e5 100644 (file)
@@ -2093,9 +2093,6 @@ void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, B
 
 CLRDATA_ADDRESS GetAppDomainForMT(CLRDATA_ADDRESS mtPtr);
 CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr);
-void GCHeapInfo(const GCHeapDetails &heap, DWORD_PTR &total_alloc_size, DWORD_PTR &total_committed_size);
-BOOL GCObjInHeap(TADDR taddrObj, const GCHeapDetails &heap,
-    TADDR_SEGINFO& trngSeg, int& gen, TADDR_RANGE& allocCtx, BOOL &bLarge);
 
 BOOL VerifyObject(const GCHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize,
     BOOL bVerifyMember);