From f553dc662acf1e1c936129dcd76c759f193a65b5 Mon Sep 17 00:00:00 2001 From: Lee Culver Date: Tue, 14 Mar 2023 17:49:20 -0700 Subject: [PATCH] Improve/reimplement !listnearobj and !gcwhere (#3748) * 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 --- eng/Version.Details.xml | 16 +- eng/Versions.props | 6 +- .../DumpHeapCommand.cs | 4 +- .../GCWhereCommand.cs | 101 ++++ .../ListNearObjCommand.cs | 365 +++++++++++++++ .../TableOutput.cs | 33 +- .../VerifyHeapCommand.cs | 21 +- src/SOS/SOS.Hosting/Commands/SOSCommand.cs | 2 - src/SOS/SOS.UnitTests/Scripts/GCPOH.script | 4 +- src/SOS/SOS.UnitTests/Scripts/GCTests.script | 10 +- src/SOS/Strike/eeheap.cpp | 436 ------------------ src/SOS/Strike/strike.cpp | 289 +----------- src/SOS/Strike/util.h | 3 - 13 files changed, 534 insertions(+), 756 deletions(-) create mode 100644 src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs create mode 100644 src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 71b882a43..069175810 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,16 +1,16 @@ - + https://github.com/dotnet/symstore - 2cf03f4bfea0712e28b0ef1df419898337abf304 + 5678da4ea42b6f48c0156be1bd5dfe85e67850d9 - + https://github.com/microsoft/clrmd - 62aa8232f49e5a199385d9da3adbde32a3e8b3e2 + 58e151bd3faa99efa5fce6ca4e63f346a5e11906 - + https://github.com/microsoft/clrmd - 62aa8232f49e5a199385d9da3adbde32a3e8b3e2 + 58e151bd3faa99efa5fce6ca4e63f346a5e11906 @@ -47,9 +47,9 @@ https://github.com/dotnet/runtime a64420c79cb63c485138f4f1352b8730f27d7b19 - + https://github.com/dotnet/source-build-reference-packages - 4a1eff2bfa79b608181eb589982a98ebbec60fc6 + a848f811a52d75494029e663cd0a799be11744fe diff --git a/eng/Versions.props b/eng/Versions.props index 6a09478c3..332a2ed30 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -16,7 +16,7 @@ - 1.0.416301 + 1.0.415602 8.0.0-preview.3.23155.1 8.0.0-preview.3.23155.1 @@ -45,7 +45,7 @@ 5.0.0 1.1.0 - 3.0.0-beta.23163.4 + 3.0.0-beta.23164.1 16.9.0-beta1.21055.5 3.0.7 @@ -67,7 +67,7 @@ 7.0.0-beta.22316.2 10.0.18362 13.0.1 - 8.0.0-alpha.1.23163.2 + 8.0.0-alpha.1.23159.2 1.2.0-beta-23117-02 diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs index ce5334f7f..b88ad9328 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs @@ -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 index 000000000..3967c4bb3 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/GCWhereCommand.cs @@ -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 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 index 000000000..f0bc90562 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ListNearObjCommand.cs @@ -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"; + } + } + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs index 51806de18..6c3d3662d 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs @@ -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}") + { } } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs index 07e2bcffd..95335a6e4 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs @@ -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"); } diff --git a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs index db4955aad..9436ac3b9 100644 --- a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs +++ b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs @@ -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 to .")] diff --git a/src/SOS/SOS.UnitTests/Scripts/GCPOH.script b/src/SOS/SOS.UnitTests/Scripts/GCPOH.script index f384c6bfd..1dfea616d 100644 --- a/src/SOS/SOS.UnitTests/Scripts/GCPOH.script +++ b/src/SOS/SOS.UnitTests/Scripts/GCPOH.script @@ -27,8 +27,8 @@ VERIFY:\s+Array: Rank 1, Number of elements 100, Type Byte\s+ SOSCOMMAND:ObjSize SOSCOMMAND:GCWhere -# we care that the Gen is 4 (POH) -VERIFY:\s+4\s+\d\s+\s+\s+\s+0x\s*\(\d+\) +# we care that the Gen is 'pinned' (POH) +VERIFY:\s*\s+\s+\s+pinned.* SOSCOMMAND:GCRoot VERIFY:.*Thread : diff --git a/src/SOS/SOS.UnitTests/Scripts/GCTests.script b/src/SOS/SOS.UnitTests/Scripts/GCTests.script index 7b79d82c0..0d83157d4 100644 --- a/src/SOS/SOS.UnitTests/Scripts/GCTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/GCTests.script @@ -40,7 +40,7 @@ VERIFY:\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 -VERIFY:\s+0\s+\d\s+\s+\s+\s+0x\s*\(\d+\) +VERIFY:\s*\s+\s+\s+0.* SOSCOMMAND:GCRoot VERIFY:.*Thread : @@ -110,7 +110,7 @@ ENDIF:CDB SOSCOMMAND:DumpStackObjects SOSCOMMAND:GCWhere \w+\s+()\s+([Gg][Cc]where!\$0_)*GCWhere\s+ # we care that the Gen is 1 -VERIFY:\s+1\s+\d\s+\s+\s+\s+0x\s*\(\d+\) +VERIFY:\s*\s+\s+\s+1.* SOSCOMMAND:GCRoot VERIFY:.*Thread : @@ -159,7 +159,7 @@ SOSCOMMAND:EEHeap -gc SOSCOMMAND:DumpStackObjects SOSCOMMAND:GCWhere \w+\s+()\s+([Gg][Cc]where!\$0_)*GCWhere\s+ # we care that the Gen is 2 -VERIFY:\s+2\s+\d\s+\s+\s+\s+0x\s*\(\d+\) +VERIFY:\s*\s+\s+\s+2.* SOSCOMMAND:GCRoot VERIFY:.*Thread : @@ -181,8 +181,8 @@ SOSCOMMAND:EEHeap -gc SOSCOMMAND:DumpStackObjects SOSCOMMAND:GCWhere \w+\s+()\s+([Gg][Cc]where!\$0_)*GCWhere\s+ -# we care that the Gen is still 2 or 0 on Windows 3.x -VERIFY:\s+[02]\s+\d\s+\s+\s+\s+0x\s*\(\d+\) +# we care that the Gen is still 2 or 0 on Windows 3.x +VERIFY:\s*\s+\s+\s+[02].* SOSCOMMAND:GCRoot VERIFY:.*Thread : diff --git a/src/SOS/Strike/eeheap.cpp b/src/SOS/Strike/eeheap.cpp index c3ba651d2..1342b35d6 100644 --- a/src/SOS/Strike/eeheap.cpp +++ b/src/SOS/Strike/eeheap.cpp @@ -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 &liveObjs, const GCHeapDetails &heap, BOOL bLarge, BOOL bPinned, const AllocInfo *pAllocInfo, GenUsageStat *genUsage) diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 463e17393..ed0969b3d 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -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 \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 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::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::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) diff --git a/src/SOS/Strike/util.h b/src/SOS/Strike/util.h index 07c78d237..fb4000200 100644 --- a/src/SOS/Strike/util.h +++ b/src/SOS/Strike/util.h @@ -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); -- 2.34.1