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