<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>
<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">
</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>
<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 -->
<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 -->
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)
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");
--- /dev/null
+// 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));
+ }
+ }
+}
--- /dev/null
+// 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";
+ }
+ }
+ }
+}
using System.Linq;
using System.Text;
using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
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}")
+ {
}
}
}
}
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;
// 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)
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");
}
[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.")]
[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>.")]
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>:
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>:
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>:
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>:
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>:
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)
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)
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)
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);