diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e15c03db7e..6dfbbd8a91 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -4,13 +4,13 @@ https://github.com/dotnet/symstore e09f81a0b38786cb20f66b589a8b88b6997a62da - + https://github.com/microsoft/clrmd - 3368bf4451a9441076595022fdff0f2bbea57b1b + 677f1b9c6fa27b1d2988eac9d51c8a5ea8698fdd - + https://github.com/microsoft/clrmd - 3368bf4451a9441076595022fdff0f2bbea57b1b + 677f1b9c6fa27b1d2988eac9d51c8a5ea8698fdd diff --git a/eng/Versions.props b/eng/Versions.props index cc637c1da6..cf0b959d03 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -45,7 +45,7 @@ 5.0.0 6.0.0 - 3.0.0-beta.23205.1 + 3.0.0-beta.23210.1 16.9.0-beta1.21055.5 3.0.7 6.0.0 diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsHelper.cs new file mode 100644 index 0000000000..fb81fa121c --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpObjGCRefsHelper.cs @@ -0,0 +1,94 @@ +// 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.Linq; +using System.Text; +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.Runtime; +using static Microsoft.Diagnostics.ExtensionCommands.TableOutput; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "dumpobjgcrefs", Help = "A helper command to implement !dumpobj -refs")] + public sealed class DumpObjGCRefsHelper : CommandBase + { + [ServiceImport] + public ClrRuntime Runtime { get; set; } + + [Argument(Name = "object")] + public string ObjectAddress { get; set; } + + public override void Invoke() + { + if (!TryParseAddress(ObjectAddress, out ulong objAddress)) + { + throw new ArgumentException($"Invalid object address: '{ObjectAddress}'", nameof(ObjectAddress)); + } + + ClrObject obj = Runtime.Heap.GetObject(objAddress); + if (!obj.IsValid) + { + Console.WriteLine($"Unable to walk object references, invalid object."); + return; + } + + ClrReference[] refs = obj.EnumerateReferencesWithFields(carefully: false, considerDependantHandles: false).ToArray(); + if (refs.Length == 0 ) + { + Console.WriteLine("GC Refs: none"); + return; + } + + Console.WriteLine("GC Refs:"); + + int fieldNameLen = Math.Max(refs.Max(r => GetFieldName(r)?.Length ?? 0), 5); + int offsetLen = Math.Max(refs.Max(r => r.Offset.ToSignedHexString().Length), 6); + + TableOutput output = new(Console, (fieldNameLen, ""), (offsetLen, ""), (16, "x12")); + output.WriteRow("Field", "Offset", "Object", "Type"); + foreach (ClrReference objRef in refs) + { + output.WriteRow(GetFieldName(objRef), objRef.Offset, new DmlDumpObj(objRef.Object), objRef.Object.Type?.Name); + } + } + + private static string GetFieldName(ClrReference objRef) + { + if (objRef.Field is null) + { + return null; + } + + if (objRef.InnerField is null) + { + return objRef.Field?.Name; + } + + StringBuilder sb = new(260); + bool foundOneFieldName = false; + + for (ClrReference? curr = objRef; curr.HasValue; curr = curr.Value.InnerField) + { + if (sb.Length > 0) + { + sb.Append('.'); + } + + string fieldName = curr.Value.Field?.Name; + if (string.IsNullOrWhiteSpace(fieldName)) + { + sb.Append("???"); + } + else + { + sb.Append(fieldName); + foundOneFieldName = true; + } + } + + // Make sure we don't just return "???.???.???" + return foundOneFieldName ? sb.ToString() : null; + } + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs new file mode 100644 index 0000000000..a1fee8ad8d --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpRuntimeTypeCommand.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.Runtime; +using static Microsoft.Diagnostics.ExtensionCommands.TableOutput; + +namespace Microsoft.Diagnostics.ExtensionCommands +{ + [Command(Name = "dumpruntimetypes", Help = "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too.")] + public sealed class DumpRuntimeTypeCommand : CommandBase + { + [ServiceImport] + public ClrRuntime Runtime { get; set; } + + public override void Invoke() + { + TableOutput output = null; + + foreach (ClrObject runtimeType in Runtime.Heap.EnumerateObjects()) + { + Console.CancellationToken.ThrowIfCancellationRequested(); + if (!runtimeType.IsValid || !runtimeType.IsRuntimeType) + { + continue; + } + + if (!runtimeType.TryReadField("m_handle", out nuint m_handle)) + { + continue; + } + + ClrAppDomain domain = null; + string typeName; + bool isMethodTable = (m_handle & 2) == 0; + if (isMethodTable) + { + // Only lookup the type if we have a MethodTable. + ClrType type = Runtime.GetTypeByMethodTable(m_handle); + typeName = type?.Name ?? $"methodtable: {m_handle:x}"; + domain = type?.Module?.AppDomain; + } + else + { + typeName = $"typehandle: {m_handle:x} (SOS does not support resolving typehandle names.)"; + } + + if (output is null) + { + output = new(Console, (16, "x12"), (16, "x12"), (16, "x12")); + output.WriteRow("Address", "Domain", "MT", "Type Name"); + } + + output.WriteRow(new DmlDumpObj(runtimeType.Address), + domain is not null ? new DmlDumpDomain(domain.Address) : null, + isMethodTable ? new DmlDumpMT(m_handle) : m_handle, + typeName); + } + + if (output is null) + { + Console.WriteLine("No System.RuntimeType objects found."); + } + } + } +} diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs index 5f35b39381..348a23eb84 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionMethodHelpers.cs @@ -1,6 +1,7 @@ // 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.Runtime; @@ -32,6 +33,8 @@ public static string ConvertToHumanReadable(this double totalBytes) return $"{updated:0.00}gb"; } + public static string ToSignedHexString(this int offset) => offset < 0 ? $"-{Math.Abs(offset):x2}" : offset.ToString("x2"); + internal static ulong FindMostCommonPointer(this IEnumerable enumerable) => (from ptr in enumerable group ptr by ptr into g diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs index c6b91c65ad..3037306841 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TableOutput.cs @@ -219,6 +219,7 @@ private static string Format(object obj, string format) long l => l.ToString(format), uint ui => ui.ToString(format), int i => i.ToString(format), + nuint uni => ((ulong)uni).ToString(format), StringBuilder sb => sb.ToString(), IEnumerable bytes => string.Join("", bytes.Select(b => b.ToString("x2"))), string s => s, @@ -246,6 +247,22 @@ public DmlDumpObj(ulong address) } } + public sealed class DmlDumpMT : DmlExec + { + public DmlDumpMT(ulong address) + : base(address, address != 0 ? $"!dumpmt /d {address:x}" : "") + { + } + } + + public sealed class DmlDumpDomain : DmlExec + { + public DmlDumpDomain(ulong address) + : base(address, address != 0 ? $"!dumpdomain /d {address:x}" : "") + { + } + } + public sealed class DmlListNearObj : DmlExec { public DmlListNearObj(ulong address) diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs index f204d69124..e866709598 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyHeapCommand.cs @@ -152,7 +152,7 @@ internal static string GetObjectCorruptionMessage(IMemoryService memory, ClrHeap 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.ObjectReferenceNotPointerAligned => $"Object {obj.Address:x} has an unaligned member at offset {corruption.Offset:x}: is not pointer aligned", 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)}", diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs new file mode 100644 index 0000000000..818532cef1 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/VerifyObjectCommand.cs @@ -0,0 +1,57 @@ +// 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 = "verifyobj", Help = "Checks the given object for signs of corruption.")] + public sealed class VerifyObjectCommand : CommandBase + { + [ServiceImport] + public ClrRuntime Runtime { get; set; } + + [ServiceImport] + public IMemoryService Memory { get; set; } + + [Argument(Name = "ObjectAddress", Help = "The object to verify.")] + public string ObjectAddress { get; set; } + + public override void Invoke() + { + if (!TryParseAddress(ObjectAddress, out ulong objAddress)) + { + throw new ArgumentException($"Invalid object address: '{ObjectAddress}'", nameof(ObjectAddress)); + } + + bool isNotCorrupt = Runtime.Heap.FullyVerifyObject(objAddress, out IEnumerable corruptionEnum); + if (isNotCorrupt) + { + Console.WriteLine($"object 0x{objAddress:x} is a valid object"); + return; + } + + ObjectCorruption[] corruption = corruptionEnum.OrderBy(r => r.Offset).ToArray(); + int offsetColWidth = Math.Max(6, corruption.Max(r => r.Offset.ToSignedHexString().Length)); + int kindColWidth = Math.Max(5, corruption.Max(ce => ce.Kind.ToString().Length)); + + TableOutput output = new(Console, (offsetColWidth, ""), (kindColWidth, "")) + { + AlignLeft = true, + }; + + output.WriteRow("Offset", "Issue", "Description"); + foreach (ObjectCorruption oc in corruption) + { + output.WriteRow(oc.Offset.ToSignedHexString(), oc.Kind, VerifyHeapCommand.GetObjectCorruptionMessage(Memory, Runtime.Heap, oc)); + } + + Console.WriteLine(); + Console.WriteLine($"{corruption.Length:n0} error{(corruption.Length == 1 ? "" : "s")} detected."); + } + } +} diff --git a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs index 355ccc3116..7f740a181d 100644 --- a/src/SOS/SOS.Hosting/Commands/SOSCommand.cs +++ b/src/SOS/SOS.Hosting/Commands/SOSCommand.cs @@ -25,7 +25,6 @@ namespace SOS.Hosting [Command(Name = "dumpmodule", DefaultOptions = "DumpModule", Help = "Displays information about a EE module structure at the specified address.")] [Command(Name = "dumpmt", DefaultOptions = "DumpMT", Help = "Displays information about a method table at the specified address.")] [Command(Name = "dumpobj", DefaultOptions = "DumpObj", Aliases = new string[] { "do" }, Help = "Displays info about an object at the specified address.")] - [Command(Name = "dumpruntimetypes", DefaultOptions = "DumpRuntimeTypes", Help = "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too.")] [Command(Name = "dumpsig", DefaultOptions = "DumpSig", Help = "Dumps the signature of a method or field specified by .")] [Command(Name = "dumpsigelem", DefaultOptions = "DumpSigElem", Help = "Dumps a single element of a signature object.")] [Command(Name = "dumpstackobjects", DefaultOptions = "DumpStackObjects", Aliases = new string[] { "dso" }, Help = "Displays all managed objects found within the bounds of the current stack.")] @@ -50,7 +49,6 @@ namespace SOS.Hosting [Command(Name = "syncblk", DefaultOptions = "SyncBlk", Help = "Displays the SyncBlock holder info.")] [Command(Name = "threadpool", DefaultOptions = "ThreadPool", Help = "Lists basic information about the thread pool.")] [Command(Name = "threadstate", DefaultOptions = "ThreadState", Help = "Pretty prints the meaning of a threads state.")] - [Command(Name = "verifyobj", DefaultOptions = "VerifyObj", Help = "Checks the object for signs of corruption.")] [Command(Name = "comstate", DefaultOptions = "COMState", Flags = CommandFlags.Windows, Help = "Lists the COM apartment model for each thread.")] [Command(Name = "dumprcw", DefaultOptions = "DumpRCW", Flags = CommandFlags.Windows, Help = "Displays information about a Runtime Callable Wrapper.")] [Command(Name = "dumpccw", DefaultOptions = "DumpCCW", Flags = CommandFlags.Windows, Help = "Displays information about a COM Callable Wrapper.")] diff --git a/src/SOS/SOS.UnitTests/Scripts/GCTests.script b/src/SOS/SOS.UnitTests/Scripts/GCTests.script index f65630b0cf..96f558f780 100644 --- a/src/SOS/SOS.UnitTests/Scripts/GCTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/GCTests.script @@ -71,8 +71,8 @@ VERIFY:\s+Fields:\s+ VERIFY:\s+\s+\s+\s+System\.String.*_string\s+ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _static\s+ VERIFY:\s+GC Refs:\s+ -VERIFY:\s+offset\s+object\s+ -VERIFY:\s+\s+\s+ +VERIFY:\s+Field\s+Offset\s+Object\s+Type\s+ +VERIFY:.*\s+\s+.* SOSCOMMAND:DumpHeap VERIFY:\s+\s+\s+\s+ @@ -141,8 +141,8 @@ VERIFY:\s+Fields:\s+ VERIFY:\s+\s+\s+\s+System\.String.*_string\s+ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _static\s+ VERIFY:\s+GC Refs:\s+ -VERIFY:\s+offset\s+object\s+ -VERIFY:\s+\s+\s+ +VERIFY:\s+Field\s+Offset\s+Object\s+Type\s+ +VERIFY:.*\s+\s+.* # Continue to the next DebugBreak CONTINUE diff --git a/src/SOS/Strike/eeheap.cpp b/src/SOS/Strike/eeheap.cpp index be7c39aa00..3b89f7023d 100644 --- a/src/SOS/Strike/eeheap.cpp +++ b/src/SOS/Strike/eeheap.cpp @@ -612,443 +612,6 @@ void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, B } } -BOOL GCHeapTraverse(const GCHeapDetails &heap, AllocInfo* pallocInfo, VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify) -{ - DWORD_PTR dwAddrSeg = 0; - DWORD_PTR dwAddr = 0; - DWORD_PTR dwAddrCurrObj = 0; - DWORD_PTR dwAddrPrevObj = 0; - size_t s, sPrev = 0; - - DacpHeapSegmentData segment; - if (heap.has_regions) - { - BOOL bPrevFree = FALSE; - for (UINT n = 0; n <= GetMaxGeneration(); n++) - { - dwAddrSeg = (DWORD_PTR)heap.generation_table[n].start_segment; - while (dwAddrSeg != 0) - { - if (IsInterrupt()) - { - ExtOut("\n"); - return FALSE; - } - if (segment.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg)); - return FALSE; - } - dwAddrCurrObj = (DWORD_PTR)segment.mem; - DWORD_PTR end_of_segment = (DWORD_PTR)segment.highAllocMark; - - while (true) - { - if (dwAddrCurrObj - SIZEOF_OBJHEADER == end_of_segment - Align(min_obj_size)) - break; - - if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment) - { - if (dwAddrCurrObj > (DWORD_PTR)end_of_segment) - { - ExtOut("curr_object: %p > heap_segment_allocated (seg: %p)\n", - SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg)); - if (dwAddrPrevObj) - { - ExtOut("Last good object: %p\n", SOS_PTR(dwAddrPrevObj)); - } - return FALSE; - } - // done with this segment - break; - } - - if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment - && dwAddrCurrObj >= end_of_segment) - { - if (dwAddrCurrObj > end_of_segment) - { - // prev_object length is too long - ExtOut("curr_object: %p > end_of_segment: %p\n", - SOS_PTR(dwAddrCurrObj), SOS_PTR(end_of_segment)); - if (dwAddrPrevObj) - { - DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj)); - } - return FALSE; - } - return FALSE; - } - - DWORD_PTR dwAddrMethTable = 0; - if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable))) - { - return FALSE; - } - - dwAddrMethTable = dwAddrMethTable & ~sos::Object::METHODTABLE_PTR_LOW_BITMASK; - if (dwAddrMethTable == 0) - { - // Is this the beginning of an allocation context? - int i; - for (i = 0; i < pallocInfo->num; i++) - { - if (dwAddrCurrObj == (DWORD_PTR)pallocInfo->array[i].alloc_ptr) - { - dwAddrCurrObj = - (DWORD_PTR)pallocInfo->array[i].alloc_limit + Align(min_obj_size); - break; - } - } - if (i < pallocInfo->num) - continue; - - // We also need to look at the gen0 alloc context. - if (dwAddrCurrObj == (DWORD_PTR)heap.generation_table[0].allocContextPtr) - { - dwAddrCurrObj = (DWORD_PTR)heap.generation_table[0].allocContextLimit + Align(min_obj_size); - continue; - } - } - - BOOL bContainsPointers; - size_t s; - BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, FALSE, s, bContainsPointers); - if (verify && bMTOk) - bMTOk = VerifyObject(heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE); - if (!bMTOk) - { - DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj)); - if (dwAddrPrevObj) - DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj)); - - ExtOut("----------------\n"); - return FALSE; - } - - pFunc(dwAddrCurrObj, s, dwAddrMethTable, token); - - // We believe we did this alignment in ObjectSize above. - assert((s & ALIGNCONST) == 0); - dwAddrPrevObj = dwAddrCurrObj; - sPrev = s; - bPrevFree = IsMTForFreeObj(dwAddrMethTable); - - dwAddrCurrObj += s; - } - dwAddrSeg = (DWORD_PTR)segment.next; - } - } - } - else - { - DWORD_PTR begin_youngest; - DWORD_PTR end_youngest; - - begin_youngest = (DWORD_PTR)heap.generation_table[0].allocation_start; - dwAddr = (DWORD_PTR)heap.ephemeral_heap_segment; - - end_youngest = (DWORD_PTR)heap.alloc_allocated; - - dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].start_segment; - dwAddr = dwAddrSeg; - - if (segment.Request(g_sos, dwAddr, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); - return FALSE; - } - - // DWORD_PTR dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()].allocation_start; - dwAddrCurrObj = (DWORD_PTR)segment.mem; - - BOOL bPrevFree = FALSE; - - while (1) - { - if (IsInterrupt()) - { - ExtOut("\n"); - return FALSE; - } - DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated; - if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment) - { - end_of_segment = end_youngest; - if (dwAddrCurrObj - SIZEOF_OBJHEADER == end_youngest - Align(min_obj_size)) - break; - } - if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment) - { - if (dwAddrCurrObj > (DWORD_PTR)end_of_segment) - { - ExtOut ("curr_object: %p > heap_segment_allocated (seg: %p)\n", - SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg)); - if (dwAddrPrevObj) { - ExtOut ("Last good object: %p\n", SOS_PTR(dwAddrPrevObj)); - } - return FALSE; - } - dwAddrSeg = (DWORD_PTR)segment.next; - if (dwAddrSeg) - { - dwAddr = dwAddrSeg; - if (segment.Request(g_sos, dwAddr, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); - return FALSE; - } - dwAddrCurrObj = (DWORD_PTR)segment.mem; - continue; - } - else - { - break; // Done Verifying Heap - } - } - - if (dwAddrSeg == (DWORD_PTR)heap.ephemeral_heap_segment - && dwAddrCurrObj >= end_youngest) - { - if (dwAddrCurrObj > end_youngest) - { - // prev_object length is too long - ExtOut("curr_object: %p > end_youngest: %p\n", - SOS_PTR(dwAddrCurrObj), SOS_PTR(end_youngest)); - if (dwAddrPrevObj) { - DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj)); - } - return FALSE; - } - return FALSE; - } - - DWORD_PTR dwAddrMethTable = 0; - if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable))) - { - return FALSE; - } - - dwAddrMethTable = dwAddrMethTable & ~sos::Object::METHODTABLE_PTR_LOW_BITMASK; - if (dwAddrMethTable == 0) - { - // Is this the beginning of an allocation context? - int i; - for (i = 0; i < pallocInfo->num; i ++) - { - if (dwAddrCurrObj == (DWORD_PTR)pallocInfo->array[i].alloc_ptr) - { - dwAddrCurrObj = - (DWORD_PTR)pallocInfo->array[i].alloc_limit + Align(min_obj_size); - break; - } - } - if (i < pallocInfo->num) - continue; - - // We also need to look at the gen0 alloc context. - if (dwAddrCurrObj == (DWORD_PTR) heap.generation_table[0].allocContextPtr) - { - dwAddrCurrObj = (DWORD_PTR) heap.generation_table[0].allocContextLimit + Align(min_obj_size); - continue; - } - } - - BOOL bContainsPointers; - BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, FALSE, s, bContainsPointers); - if (verify && bMTOk) - bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE); - if (!bMTOk) - { - DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj)); - if (dwAddrPrevObj) - DMLOut("Last good object: %s\n", DMLObject(dwAddrPrevObj)); - - ExtOut ("----------------\n"); - return FALSE; - } - - pFunc (dwAddrCurrObj, s, dwAddrMethTable, token); - - // We believe we did this alignment in ObjectSize above. - assert((s & ALIGNCONST) == 0); - dwAddrPrevObj = dwAddrCurrObj; - sPrev = s; - bPrevFree = IsMTForFreeObj(dwAddrMethTable); - - dwAddrCurrObj += s; - } - } - - // Now for the large object and pinned object generations: - - BOOL bPinnedDone = FALSE; - - dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].start_segment; - dwAddr = dwAddrSeg; - - if (segment.Request(g_sos, dwAddr, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); - return FALSE; - } - - // dwAddrCurrObj = (DWORD_PTR)heap.generation_table[GetMaxGeneration()+1].allocation_start; - dwAddrCurrObj = (DWORD_PTR)segment.mem; - - dwAddrPrevObj=0; - - while(1) - { - if (IsInterrupt()) - { - ExtOut("\n"); - return FALSE; - } - - DWORD_PTR end_of_segment = (DWORD_PTR)segment.allocated; - - if (dwAddrCurrObj >= (DWORD_PTR)end_of_segment) - { - if (dwAddrCurrObj > (DWORD_PTR)end_of_segment) - { - ExtOut("curr_object: %p > heap_segment_allocated (seg: %p)\n", - SOS_PTR(dwAddrCurrObj), SOS_PTR(dwAddrSeg)); - if (dwAddrPrevObj) { - ExtOut("Last good object: %p\n", SOS_PTR(dwAddrPrevObj)); - } - return FALSE; - } - - dwAddrSeg = (DWORD_PTR)segment.next; - if (dwAddrSeg) - { - dwAddr = dwAddrSeg; - if (segment.Request(g_sos, dwAddr, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); - return FALSE; - } - dwAddrCurrObj = (DWORD_PTR)segment.mem; - continue; - } - else if (heap.has_poh && !bPinnedDone) - { - bPinnedDone = TRUE; - dwAddrSeg = (DWORD_PTR)heap.generation_table[GetMaxGeneration() + 2].start_segment; - dwAddr = dwAddrSeg; - - if (segment.Request(g_sos, dwAddr, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddr)); - return FALSE; - } - - dwAddrCurrObj = (DWORD_PTR)segment.mem; - continue; - } - else - { - break; // Done Verifying Heap - } - } - - DWORD_PTR dwAddrMethTable = 0; - if (FAILED(GetMTOfObject(dwAddrCurrObj, &dwAddrMethTable))) - { - return FALSE; - } - - dwAddrMethTable = dwAddrMethTable & ~sos::Object::METHODTABLE_PTR_LOW_BITMASK; - BOOL bContainsPointers; - BOOL bMTOk = GetSizeEfficient(dwAddrCurrObj, dwAddrMethTable, TRUE, s, bContainsPointers); - if (verify && bMTOk) - bMTOk = VerifyObject (heap, dwAddrCurrObj, dwAddrMethTable, s, TRUE); - if (!bMTOk) - { - DMLOut("curr_object: %s\n", DMLListNearObj(dwAddrCurrObj)); - - if (dwAddrPrevObj) - DMLOut("Last good object: %s\n", dwAddrPrevObj); - - ExtOut ("----------------\n"); - return FALSE; - } - - pFunc (dwAddrCurrObj, s, dwAddrMethTable, token); - - // We believe we did this alignment in ObjectSize above. - assert((s & ALIGNCONSTLARGE) == 0); - dwAddrPrevObj = dwAddrCurrObj; - dwAddrCurrObj += s; - } - - return TRUE; -} - -BOOL GCHeapsTraverse(VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify) -{ - // Obtain allocation context for each managed thread. - AllocInfo allocInfo; - allocInfo.Init(); - - if (!IsServerBuild()) - { - DacpGcHeapDetails dacHeapDetails; - if (dacHeapDetails.Request(g_sos) != S_OK) - { - ExtOut("Error requesting gc heap details\n"); - return FALSE; - } - - GCHeapDetails heapDetails(dacHeapDetails); - return GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify); - } - else - { - DacpGcHeapData gcheap; - if (gcheap.Request(g_sos) != S_OK) - { - ExtOut("Error requesting GC Heap data\n"); - return FALSE; - } - - DWORD dwAllocSize; - DWORD dwNHeaps = gcheap.HeapCount; - if (!ClrSafeInt::multiply(sizeof(CLRDATA_ADDRESS), dwNHeaps, dwAllocSize)) - { - ExtOut("Failed to get GCHeaps: integer overflow error\n"); - return FALSE; - } - CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize); - if (g_sos->GetGCHeapList(dwNHeaps, heapAddrs, NULL) != S_OK) - { - ExtOut("Failed to get GCHeaps\n"); - return FALSE; - } - - DWORD n; - for (n = 0; n < dwNHeaps; n ++) - { - DacpGcHeapDetails dacHeapDetails; - if (dacHeapDetails.Request(g_sos, heapAddrs[n]) != S_OK) - { - ExtOut("Error requesting details\n"); - return FALSE; - } - - GCHeapDetails heapDetails(dacHeapDetails, heapAddrs[n]); - if (!GCHeapTraverse (heapDetails, &allocInfo, pFunc, token, verify)) - { - ExtOut("Traversing a gc heap failed\n"); - return FALSE; - } - } - } - - return TRUE; -} - GCHeapSnapshot::GCHeapSnapshot() { m_isBuilt = FALSE; diff --git a/src/SOS/Strike/gcroot.cpp b/src/SOS/Strike/gcroot.cpp index 34df3bc577..8a426ca7c3 100644 --- a/src/SOS/Strike/gcroot.cpp +++ b/src/SOS/Strike/gcroot.cpp @@ -143,686 +143,3 @@ UINT FindAllPinnedAndStrong(DWORD_PTR handlearray[], UINT arraySize) return pos; } - - -//////////////////////////////////////////////////////////////////////////////// -// -// Some defines for cards taken from gc code -// -#define card_word_width ((size_t)32) - -// -// The value of card_size is determined empirically according to the average size of an object -// In the code we also rely on the assumption that one card_table entry (DWORD) covers an entire os page -// -#if defined (_TARGET_WIN64_) -#define card_size ((size_t)(2*DT_GC_PAGE_SIZE/card_word_width)) -#else -#define card_size ((size_t)(DT_GC_PAGE_SIZE/card_word_width)) -#endif //_TARGET_WIN64_ - -// so card_size = 128 on x86, 256 on x64 - -inline -size_t card_word (size_t card) -{ - return card / card_word_width; -} - -inline -unsigned card_bit (size_t card) -{ - return (unsigned)(card % card_word_width); -} - -inline -size_t card_of ( BYTE* object) -{ - return (size_t)(object) / card_size; -} - -BOOL CardIsSet(const GCHeapDetails &heap, TADDR objAddr) -{ - // The card table has to be translated to look at the refcount, etc. - // g_card_table[card_word(card_of(g_lowest_address))]. - - TADDR card_table = TO_TADDR(heap.card_table); - card_table = card_table + card_word(card_of((BYTE *)heap.lowest_address))*sizeof(DWORD); - - do - { - TADDR card_table_lowest_addr; - TADDR card_table_next; - - if (MOVE(card_table_lowest_addr, ALIGN_DOWN(card_table, 0x1000) + sizeof(PVOID)) != S_OK) - { - ExtErr("Error getting card table lowest address\n"); - return FALSE; - } - - if (MOVE(card_table_next, card_table - sizeof(PVOID)) != S_OK) - { - ExtErr("Error getting next card table\n"); - return FALSE; - } - - size_t card = (objAddr - card_table_lowest_addr) / card_size; - TADDR card_addr = card_table + (card_word(card) * sizeof(DWORD)); - DWORD value; - if (MOVE(value, card_addr) != S_OK) - { - ExtErr("Error reading card bits - obj %p card %08x card_addr %p card_table %p\n", objAddr, card, card_addr, card_table); - return FALSE; - } - - if (value & 1<= heap.background_saved_lowest_address) && (o < heap.background_saved_highest_address)) - m = mark_array_marked(heap, o); - - return m; -} - -BOOL fgc_should_consider_object(const GCHeapDetails &heap, - CLRDATA_ADDRESS o, - const DacpHeapSegmentData &seg, - BOOL consider_bgc_mark_p, - BOOL check_current_sweep_p, - BOOL check_saved_sweep_p) -{ - // the logic for this function must be kept in sync with the analogous function in gc.cpp - BOOL no_bgc_mark_p = FALSE; - - if (consider_bgc_mark_p) - { - if (check_current_sweep_p && (o < heap.next_sweep_obj)) - { - no_bgc_mark_p = TRUE; - } - - if (!no_bgc_mark_p) - { - if(check_saved_sweep_p && (o >= heap.saved_sweep_ephemeral_start)) - { - no_bgc_mark_p = TRUE; - } - - if (!check_saved_sweep_p) - { - CLRDATA_ADDRESS background_allocated = seg.background_allocated; - if (o >= background_allocated) - { - no_bgc_mark_p = TRUE; - } - } - } - } - else - { - no_bgc_mark_p = TRUE; - } - - return no_bgc_mark_p ? TRUE : background_object_marked(heap, o); -} - -enum c_gc_state -{ - c_gc_state_marking, - c_gc_state_planning, - c_gc_state_free -}; - -inline BOOL in_range_for_segment(const DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr) -{ - return (addr >= seg.mem) && (addr < seg.reserved); -} - -void should_check_bgc_mark(const GCHeapDetails &heap, - const DacpHeapSegmentData &seg, - BOOL* consider_bgc_mark_p, - BOOL* check_current_sweep_p, - BOOL* check_saved_sweep_p) -{ - // the logic for this function must be kept in sync with the analogous function in gc.cpp - *consider_bgc_mark_p = FALSE; - *check_current_sweep_p = FALSE; - *check_saved_sweep_p = FALSE; - - if (heap.current_c_gc_state == c_gc_state_planning) - { - // We are doing the next_sweep_obj comparison here because we have yet to - // turn on the swept flag for the segment but in_range_for_segment will return - // FALSE if the address is the same as reserved. - if ((seg.flags & heap_segment_flags_swept) || (heap.next_sweep_obj == seg.reserved)) - { - // this seg was already swept. - } - else - { - *consider_bgc_mark_p = TRUE; - - if ((heap.saved_sweep_ephemeral_seg != -1) && (seg.segmentAddr == heap.saved_sweep_ephemeral_seg)) - { - *check_saved_sweep_p = TRUE; - } - - if (in_range_for_segment(seg, heap.next_sweep_obj)) - { - *check_current_sweep_p = TRUE; - } - } - } -} - -// TODO: FACTOR TOGETHER THE OBJECT MEMBER WALKING CODE FROM -// TODO: VerifyObjectMember(), GetListOfRefs(), HeapTraverser::PrintRefs() -BOOL VerifyObjectMember(const GCHeapDetails &heap, DWORD_PTR objAddr) -{ - BOOL ret = TRUE; - BOOL bCheckCard = TRUE; - size_t size = 0; - { - DWORD_PTR dwAddrCard = objAddr; - while (dwAddrCard < objAddr + size) - { - if (CardIsSet(heap, dwAddrCard)) - { - bCheckCard = FALSE; - break; - } - dwAddrCard += card_size; - } - - if (bCheckCard) - { - dwAddrCard = objAddr + size - 2*sizeof(PVOID); - if (CardIsSet(heap, dwAddrCard)) - { - bCheckCard = FALSE; - } - } - } - - for (sos::RefIterator itr(TO_TADDR(objAddr)); itr; ++itr) - { - TADDR dwAddr1 = (DWORD_PTR)*itr; - if (dwAddr1) - { - TADDR dwChild = dwAddr1; - // Try something more efficient than IsObject here. Is the methodtable valid? - size_t s; - BOOL bPointers; - TADDR dwAddrMethTable; - if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) || - (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE)) - { - DMLOut("object %s: bad member %p at %p\n", DMLObject(objAddr), SOS_PTR(dwAddr1), SOS_PTR(itr.GetOffset())); - ret = FALSE; - } - - if (IsMTForFreeObj(dwAddrMethTable)) - { - DMLOut("object %s contains free object %p at %p\n", DMLObject(objAddr), - SOS_PTR(dwAddr1), SOS_PTR(objAddr+itr.GetOffset())); - ret = FALSE; - } - - // verify card table - if (bCheckCard && NeedCard(objAddr+itr.GetOffset(), dwAddr1)) - { - DMLOut("object %s:%s missing card_table entry for %p\n", - DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe", - SOS_PTR(objAddr+itr.GetOffset())); - ret = FALSE; - } - } - } - - return ret; -} - -// search for can_verify_deep in gc.cpp for examples of how these functions are used. -BOOL VerifyObject(const GCHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, - BOOL bVerifyMember) -{ - if (IsMTForFreeObj(MTAddr)) - { - return TRUE; - } - - if (objSize < min_obj_size) - { - DMLOut("object %s: size %d too small\n", DMLObject(objAddr), objSize); - return FALSE; - } - - // If we requested to verify the object's members, the GC may be in a state where that's not possible. - // Here we check to see if the object in question needs to have its members updated. If so, we turn off - // verification for the object. - if (bVerifyMember) - { - BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE; - should_check_bgc_mark(heap, seg, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep); - bVerifyMember = fgc_should_consider_object(heap, objAddr, seg, consider_bgc_mark, check_current_sweep, check_saved_sweep); - } - - return bVerifyMember ? VerifyObjectMember(heap, objAddr) : TRUE; -} - - -BOOL FindSegment(const GCHeapDetails &heap, DacpHeapSegmentData &seg, CLRDATA_ADDRESS addr) -{ - if (heap.has_regions) - { - CLRDATA_ADDRESS dwAddrSeg; - for (UINT n = 0; n <= GetMaxGeneration(); n++) - { - dwAddrSeg = (DWORD_PTR)heap.generation_table[n].start_segment; - while (dwAddrSeg != 0) - { - if (IsInterrupt()) - return FALSE; - if (seg.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p\n", SOS_PTR(dwAddrSeg)); - return FALSE; - } - if (addr >= TO_TADDR(seg.mem) && addr < seg.highAllocMark) - { - return TRUE; - } - dwAddrSeg = (DWORD_PTR)seg.next; - } - } - return FALSE; - } - else - { - CLRDATA_ADDRESS dwAddrSeg = heap.generation_table[GetMaxGeneration()].start_segment; - - // Request the initial segment. - if (seg.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg)); - return FALSE; - } - - // Loop while the object is not in range of the segment. - while (addr < TO_TADDR(seg.mem) || - addr >= (dwAddrSeg == heap.ephemeral_heap_segment ? heap.alloc_allocated : TO_TADDR(seg.allocated))) - { - // get the next segment - dwAddrSeg = seg.next; - - // We reached the last segment without finding the object. - if (dwAddrSeg == NULL) - return FALSE; - - if (seg.Request(g_sos, dwAddrSeg, heap.original_heap_details) != S_OK) - { - ExtOut("Error requesting heap segment %p.\n", SOS_PTR(dwAddrSeg)); - return FALSE; - } - } - } - - return TRUE; -} - -BOOL VerifyObject(const GCHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, BOOL bVerifyMember) -{ - // This is only used by the other VerifyObject function if bVerifyMember is true, - // so we only initialize it if we need it for verifying object members. - DacpHeapSegmentData seg; - - if (bVerifyMember) - { - // if we fail to find the segment, we cannot verify the object's members - bVerifyMember = FindSegment(heap, seg, objAddr); - } - - return VerifyObject(heap, seg, objAddr, MTAddr, objSize, bVerifyMember); -} - -void sos::ObjectIterator::BuildError(char *out, size_t count, const char *format, ...) const -{ - if (out == NULL || count == 0) - return; - - va_list args; - va_start(args, format); - - int written = vsprintf_s(out, count, format, args); - if (written > 0 && mLastObj) - sprintf_s(out+written, count-written, "\nLast good object: %p.\n", (int*)mLastObj); - - va_end(args); -} - -bool sos::ObjectIterator::VerifyObjectMembers(char *reason, size_t count) const -{ - if (!mCurrObj.HasPointers()) - return true; - - size_t size = mCurrObj.GetSize(); - size_t objAddr = (size_t)mCurrObj.GetAddress(); - TADDR mt = mCurrObj.GetMT(); - - INT_PTR nEntries; - MOVE(nEntries, mt-sizeof(PVOID)); - if (nEntries < 0) - nEntries = -nEntries; - - size_t nSlots = 1 + nEntries * sizeof(CGCDescSeries)/sizeof(DWORD_PTR); - ArrayHolder buffer = new DWORD_PTR[nSlots]; - - if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(mt - nSlots*sizeof(DWORD_PTR)), - buffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL))) - { - BuildError(reason, count, "Object %s has a bad GCDesc.", DMLObject(objAddr)); - return false; - } - - CGCDesc *map = (CGCDesc *)(buffer+nSlots); - CGCDescSeries* cur = map->GetHighestSeries(); - CGCDescSeries* last = map->GetLowestSeries(); - - const size_t bufferSize = sizeof(size_t)*128; - size_t objBuffer[bufferSize/sizeof(size_t)]; - size_t dwBeginAddr = (size_t)objAddr; - size_t bytesInBuffer = bufferSize; - if (size < bytesInBuffer) - bytesInBuffer = size; - - - if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer,NULL))) - { - BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr)); - return false; - } - - BOOL bCheckCard = TRUE; - { - DWORD_PTR dwAddrCard = (DWORD_PTR)objAddr; - while (dwAddrCard < objAddr + size) - { - if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard)) - { - bCheckCard = FALSE; - break; - } - dwAddrCard += card_size; - } - if (bCheckCard) - { - dwAddrCard = objAddr + size - 2*sizeof(PVOID); - if (CardIsSet (mHeaps[mCurrHeap], dwAddrCard)) - { - bCheckCard = FALSE; - } - } - } - - if (cur >= last) - { - do - { - BYTE** parm = (BYTE**)((objAddr) + cur->GetSeriesOffset()); - BYTE** ppstop = - (BYTE**)((BYTE*)parm + cur->GetSeriesSize() + (size)); - while (parm < ppstop) - { - CheckInterrupt(); - size_t dwAddr1; - - // Do we run out of cache? - if ((size_t)parm >= dwBeginAddr+bytesInBuffer) - { - // dwBeginAddr += bytesInBuffer; - dwBeginAddr = (size_t)parm; - if (dwBeginAddr >= objAddr + size) - { - return true; - } - bytesInBuffer = bufferSize; - if (objAddr+size-dwBeginAddr < bytesInBuffer) - { - bytesInBuffer = objAddr+size-dwBeginAddr; - } - if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL))) - { - BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr)); - return false; - } - } - dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)]; - if (dwAddr1) { - DWORD_PTR dwChild = dwAddr1; - // Try something more efficient than IsObject here. Is the methodtable valid? - size_t s; - BOOL bPointers; - DWORD_PTR dwAddrMethTable; - if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) || - (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE)) - { - BuildError(reason, count, "object %s: bad member %p at %p", DMLObject(objAddr), - SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); - - return false; - } - - if (IsMTForFreeObj(dwAddrMethTable)) - { - sos::Throw("object %s contains free object %p at %p", DMLObject(objAddr), - SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); - } - - // verify card table - if (bCheckCard && - NeedCard(objAddr+(size_t)parm-objAddr,dwChild)) - { - BuildError(reason, count, "Object %s: %s missing card_table entry for %p", - DMLObject(objAddr), (dwChild == dwAddr1)? "" : " maybe", - SOS_PTR(objAddr+(size_t)parm-objAddr)); - - return false; - } - } - parm++; - } - cur--; - CheckInterrupt(); - - } while (cur >= last); - } - else - { - int cnt = (int) map->GetNumSeries(); - BYTE** parm = (BYTE**)((objAddr) + cur->startoffset); - while ((BYTE*)parm < (BYTE*)((objAddr)+(size)-plug_skew)) - { - for (int __i = 0; __i > cnt; __i--) - { - CheckInterrupt(); - - unsigned skip = cur->val_serie[__i].skip; - unsigned nptrs = cur->val_serie[__i].nptrs; - BYTE** ppstop = parm + nptrs; - do - { - size_t dwAddr1; - // Do we run out of cache? - if ((size_t)parm >= dwBeginAddr+bytesInBuffer) - { - // dwBeginAddr += bytesInBuffer; - dwBeginAddr = (size_t)parm; - if (dwBeginAddr >= objAddr + size) - return true; - - bytesInBuffer = bufferSize; - if (objAddr+size-dwBeginAddr < bytesInBuffer) - bytesInBuffer = objAddr+size-dwBeginAddr; - - if (FAILED(g_ExtData->ReadVirtual(TO_CDADDR(dwBeginAddr), objBuffer, (ULONG) bytesInBuffer, NULL))) - { - BuildError(reason, count, "Object %s: Failed to read members.", DMLObject(objAddr)); - return false; - } - } - dwAddr1 = objBuffer[((size_t)parm-dwBeginAddr)/sizeof(size_t)]; - { - if (dwAddr1) - { - DWORD_PTR dwChild = dwAddr1; - // Try something more efficient than IsObject here. Is the methodtable valid? - size_t s; - BOOL bPointers; - DWORD_PTR dwAddrMethTable; - if (FAILED(GetMTOfObject(dwAddr1, &dwAddrMethTable)) || - (GetSizeEfficient(dwAddr1, dwAddrMethTable, FALSE, s, bPointers) == FALSE)) - { - BuildError(reason, count, "Object %s: Bad member %p at %p.\n", DMLObject(objAddr), - SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); - - return false; - } - - if (IsMTForFreeObj(dwAddrMethTable)) - { - BuildError(reason, count, "Object %s contains free object %p at %p.", DMLObject(objAddr), - SOS_PTR(dwAddr1), SOS_PTR(objAddr+(size_t)parm-objAddr)); - return false; - } - - // verify card table - if (bCheckCard && - NeedCard (objAddr+(size_t)parm-objAddr,dwAddr1)) - { - BuildError(reason, count, "Object %s:%s missing card_table entry for %p", - DMLObject(objAddr), (dwChild == dwAddr1) ? "" : " maybe", - SOS_PTR(objAddr+(size_t)parm-objAddr)); - - return false; - } - } - } - parm++; - CheckInterrupt(); - } while (parm < ppstop); - parm = (BYTE**)((BYTE*)parm + skip); - } - } - } - - return true; -} - -bool sos::ObjectIterator::Verify(char *reason, size_t count) const -{ - try - { - TADDR mt = mCurrObj.GetMT(); - - if (MethodTable::GetFreeMT() == mt) - { - return true; - } - - size_t size = mCurrObj.GetSize(); - if (size < min_obj_size) - { - BuildError(reason, count, "Object %s: Size %d is too small.", DMLObject(mCurrObj.GetAddress()), size); - return false; - } - - if (mCurrObj.GetAddress() + mCurrObj.GetSize() > mSegmentEnd) - { - BuildError(reason, count, "Object %s is too large. End of segment at %p.", DMLObject(mCurrObj), mSegmentEnd); - return false; - } - - BOOL bVerifyMember = TRUE; - - // If we requested to verify the object's members, the GC may be in a state where that's not possible. - // Here we check to see if the object in question needs to have its members updated. If so, we turn off - // verification for the object. - BOOL consider_bgc_mark = FALSE, check_current_sweep = FALSE, check_saved_sweep = FALSE; - should_check_bgc_mark(mHeaps[mCurrHeap], mSegment, &consider_bgc_mark, &check_current_sweep, &check_saved_sweep); - bVerifyMember = fgc_should_consider_object(mHeaps[mCurrHeap], mCurrObj.GetAddress(), mSegment, - consider_bgc_mark, check_current_sweep, check_saved_sweep); - - if (bVerifyMember) - return VerifyObjectMembers(reason, count); - } - catch(const sos::Exception &e) - { - BuildError(reason, count, e.GetMesssage()); - return false; - } - - return true; -} - -bool sos::ObjectIterator::Verify() const -{ - char *c = NULL; - return Verify(c, 0); -} diff --git a/src/SOS/Strike/sos.cpp b/src/SOS/Strike/sos.cpp index 33368fe60b..d637289253 100644 --- a/src/SOS/Strike/sos.cpp +++ b/src/SOS/Strike/sos.cpp @@ -376,187 +376,6 @@ namespace sos return (size_t)stInfo.m_StringLength; } - - RefIterator::RefIterator(TADDR obj, LinearReadCache *cache) - : mCache(cache), mGCDesc(0), mArrayOfVC(false), mDone(false), mBuffer(0), mCurrSeries(0), mLoaderAllocatorObjectHandle(0), - i(0), mCount(0), mCurr(0), mStop(0), mObject(obj), mObjSize(0) - { - Init(); - } - - RefIterator::RefIterator(TADDR obj, CGCDesc *desc, bool arrayOfVC, LinearReadCache *cache) - : mCache(cache), mGCDesc(desc), mArrayOfVC(arrayOfVC), mDone(false), mBuffer(0), mCurrSeries(0), mLoaderAllocatorObjectHandle(0), - i(0), mCount(0), mCurr(0), mStop(0), mObject(obj), mObjSize(0) - { - Init(); - } - - RefIterator::~RefIterator() - { - if (mBuffer) - delete [] mBuffer; - } - - const RefIterator &RefIterator::operator++() - { - if (mDone) - Throw("Attempt to move past the end of the iterator."); - - if (mCurr == mLoaderAllocatorObjectHandle) - { - // The mLoaderAllocatorObjectHandle is always the last reference returned - mDone = true; - return *this; - } - - if (!mArrayOfVC) - { - mCurr += sizeof(TADDR); - if (mCurr >= mStop) - { - mCurrSeries--; - if (mCurrSeries < mGCDesc->GetLowestSeries()) - { - mDone = true; - } - else - { - mCurr = mObject + mCurrSeries->GetSeriesOffset(); - mStop = mCurr + mCurrSeries->GetSeriesSize() + mObjSize; - } - } - } - else - { - mCurr += sizeof(TADDR); - if (mCurr >= mStop) - { - int i_last = i; - i--; - - if (i == mCount) - i = 0; - - mCurr += mCurrSeries->val_serie[i_last].skip; - mStop = mCurr + mCurrSeries->val_serie[i].nptrs * sizeof(TADDR); - } - - if (mCurr >= mObject + mObjSize - plug_skew) - mDone = true; - } - - if (mDone && mLoaderAllocatorObjectHandle != NULL) - { - // The iteration over all regular object references is done, but there is one more - // reference for collectible types - the LoaderAllocator for GC - mCurr = mLoaderAllocatorObjectHandle; - mDone = false; - } - - return *this; - } - - TADDR RefIterator::operator*() const - { - return ReadPointer(mCurr); - } - - TADDR RefIterator::GetOffset() const - { - return mCurr - mObject; - } - - void RefIterator::Init() - { - TADDR mt = ReadPointer(mObject); - BOOL bContainsPointers = FALSE; - BOOL bCollectible = FALSE; - TADDR loaderAllocatorObjectHandle; - - if (!GetSizeEfficient(mObject, mt, FALSE, mObjSize, bContainsPointers)) - Throw("Failed to get size of object."); - - if (!GetCollectibleDataEfficient(mt, bCollectible, loaderAllocatorObjectHandle)) - Throw("Failed to get collectible info of object."); - - if (!bContainsPointers && !bCollectible) - { - mDone = true; - return; - } - - if (bContainsPointers) - { - if (!mGCDesc) - { - int entries = 0; - - if (FAILED(MOVE(entries, mt-sizeof(TADDR)))) - Throw("Failed to request number of entries for %p MT %p", mObject, mt); - - // array of vc? - if (entries < 0) - { - entries = -entries; - mArrayOfVC = true; - } - else - { - mArrayOfVC = false; - } - - size_t slots = 1 + entries * sizeof(CGCDescSeries)/sizeof(TADDR); - - ArrayHolder buffer = new TADDR[slots]; - - ULONG fetched = 0; - CLRDATA_ADDRESS address = TO_CDADDR(mt - slots*sizeof(TADDR)); - if (FAILED(g_ExtData->ReadVirtual(address, buffer, (ULONG)(slots*sizeof(TADDR)), &fetched))) - Throw("Failed to request GCDesc."); - - mBuffer = buffer.Detach(); - mGCDesc = (CGCDesc*)(mBuffer + slots); - } - - mCurrSeries = mGCDesc->GetHighestSeries(); - - if (!mArrayOfVC) - { - mCurr = mObject + mCurrSeries->GetSeriesOffset(); - mStop = mCurr + mCurrSeries->GetSeriesSize() + mObjSize; - } - else - { - i = 0; - mCurr = mObject + mCurrSeries->startoffset; - mStop = mCurr + mCurrSeries->val_serie[i].nptrs * sizeof(TADDR); - mCount = (int)mGCDesc->GetNumSeries(); - } - - if (mCurr == mStop) - operator++(); - else if (mCurr >= mObject + mObjSize - plug_skew) - mDone = true; - } - else - { - mDone = true; - } - - if (bCollectible) - { - mLoaderAllocatorObjectHandle = loaderAllocatorObjectHandle; - if (mDone) - { - // There are no object references, but there is still a reference for - // collectible types - the LoaderAllocator for GC - mCurr = mLoaderAllocatorObjectHandle; - mDone = false; - } - } - } - - const TADDR GCHeap::HeapStart = 0; const TADDR GCHeap::HeapEnd = ~0; diff --git a/src/SOS/Strike/sos.h b/src/SOS/Strike/sos.h index b514244d95..c0fed3a3b2 100644 --- a/src/SOS/Strike/sos.h +++ b/src/SOS/Strike/sos.h @@ -452,77 +452,6 @@ namespace sos mutable WCHAR *mTypeName; }; - /* Enumerates all the GC references (objects) contained in an object. This uses the GCDesc - * map exactly as the GC does. - */ - class RefIterator - { - public: - RefIterator(TADDR obj, LinearReadCache *cache = NULL); - RefIterator(TADDR obj, CGCDesc *desc, bool arrayOfVC, LinearReadCache *cache = NULL); - ~RefIterator(); - - /* Moves to the next reference in the object. - */ - const RefIterator &operator++(); - - /* Returns the address of the current reference. - */ - TADDR operator*() const; - - /* Gets the offset into the object where the current reference comes from. - */ - TADDR GetOffset() const; - - /* Returns true if there are more objects in the iteration, false otherwise. - * Used as: - * if (itr) - * ... - */ - inline operator void *() const - { - return (void*)!mDone; - } - - bool IsLoaderAllocator() const - { - return mLoaderAllocatorObjectHandle == mCurr; - } - - private: - void Init(); - inline TADDR ReadPointer(TADDR addr) const - { - if (mCache) - { - if (!mCache->Read(addr, &addr, false)) - Throw("Could not read address %p.", addr); - } - else - { - MOVE(addr, addr); - } - - return addr; - } - - private: - LinearReadCache *mCache; - CGCDesc *mGCDesc; - bool mArrayOfVC, mDone; - - TADDR *mBuffer; - CGCDescSeries *mCurrSeries; - - TADDR mLoaderAllocatorObjectHandle; - - int i, mCount; - - TADDR mCurr, mStop, mObject; - size_t mObjSize; - }; - - /* The Iterator used to walk the managed objects on the GC heap. * The general usage pattern for this class is: * for (ObjectIterator itr = gcheap.WalkHeap(); itr; ++itr) @@ -580,27 +509,6 @@ namespace sos return bLarge; } - /* Verifies the current object. Returns true if the current object is valid. - * Returns false and fills 'buffer' with the reason the object is corrupted. - * This is a deeper validation than Object::IsValid as it checks the card - * table entires for the object in addition to the rest of the references. - * This function does not throw exceptions. - * Params: - * buffer - out buffer that is filled if and only if this function returns - * false. - * size - the total size of the buffer - * Returns: - * True if the object is valid, false otherwise. - */ - bool Verify(__out_ecount(size) char *buffer, size_t size) const; - - /* The same as Verify(char*, size_t), except it does not write out the failure - * reason to a provided buffer. - * See: - * ObjectIterator::Verify(char *, size_t) - */ - bool Verify() const; - /* Attempts to move to the next object (similar to ObjectIterator++), but * attempts to recover from any heap corruption by skipping to the next * segment. If Verify returns false, meaning it detected heap corruption @@ -622,9 +530,6 @@ namespace sos private: ObjectIterator(const GCHeapDetails *heap, int numHeaps, TADDR start, TADDR stop); - bool VerifyObjectMembers(__out_ecount(size) char *buffer, size_t size) const; - void BuildError(__out_ecount(count) char *out, size_t count, const char *format, ...) const; - void AssertSanity() const; /* diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 08b39a1a23..410a8ea409 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -2192,11 +2192,9 @@ DECLARE_API(DumpObj) if (SUCCEEDED(Status) && bRefs) { - ExtOut("GC Refs:\n"); - TableOutput out(2, POINTERSIZE_HEX, AlignRight, 4); - out.WriteRow("offset", "object"); - for (sos::RefIterator itr(TO_TADDR(p_Object)); itr; ++itr) - out.WriteRow(Hex(itr.GetOffset()), ObjectPtr(*itr)); + std::stringstream argsBuilder; + argsBuilder << std::hex << p_Object << " "; + return ExecuteCommand("dumpobjgcrefs", argsBuilder.str().c_str()); } } catch(const sos::Exception &e) @@ -3652,114 +3650,11 @@ DECLARE_API(TraverseHeap) return ExecuteCommand("traverseheap", args); } -struct PrintRuntimeTypeArgs -{ - DWORD_PTR mtOfRuntimeType; - int handleFieldOffset; - DacpAppDomainStoreData adstore; -}; - -void PrintRuntimeTypes(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable,LPVOID token) -{ - PrintRuntimeTypeArgs *pArgs = (PrintRuntimeTypeArgs *)token; - - if (pArgs->mtOfRuntimeType == NULL) - { - NameForMT_s(methodTable, g_mdName, mdNameLen); - - if (_wcscmp(g_mdName, W("System.RuntimeType")) == 0) - { - pArgs->mtOfRuntimeType = methodTable; - pArgs->handleFieldOffset = GetObjFieldOffset(TO_CDADDR(objAddr), TO_CDADDR(methodTable), W("m_handle")); - if (pArgs->handleFieldOffset <= 0) - ExtOut("Error getting System.RuntimeType.m_handle offset\n"); - - pArgs->adstore.Request(g_sos); - } - } - - if ((methodTable == pArgs->mtOfRuntimeType) && (pArgs->handleFieldOffset > 0)) - { - // Get the method table and display the information. - DWORD_PTR mtPtr; - if (MOVE(mtPtr, objAddr + pArgs->handleFieldOffset) == S_OK) - { - DMLOut(DMLObject(objAddr)); - - // Check if TypeDesc - if ((mtPtr & RUNTIMETYPE_HANDLE_IS_TYPEDESC) != 0) - { - ExtOut(" %p\n", mtPtr & ~RUNTIMETYPE_HANDLE_IS_TYPEDESC); - } - else - { - CLRDATA_ADDRESS appDomain = GetAppDomainForMT(mtPtr); - if (appDomain != NULL) - { - if (appDomain == pArgs->adstore.sharedDomain) - ExtOut(" %" POINTERSIZE "s", "Shared"); - - else if (appDomain == pArgs->adstore.systemDomain) - ExtOut(" %" POINTERSIZE "s", "System"); - else - DMLOut(" %s", DMLDomain(appDomain)); - } - else - { - ExtOut(" %" POINTERSIZE "s", "?"); - } - - if (NameForMT_s(mtPtr, g_mdName, mdNameLen)) - { - DMLOut(" %s %S\n", DMLMethodTable(mtPtr), g_mdName); - } - } - } - } -} - - DECLARE_API(DumpRuntimeTypes) { - INIT_API(); + INIT_API_EXT(); MINIDUMP_NOT_SUPPORTED(); - - BOOL dml = FALSE; - - CMDOption option[] = - { // name, vptr, type, hasValue - {"/d", &dml, COBOOL, FALSE}, - }; - - if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL)) - return Status; - - EnableDMLHolder dmlHolder(dml); - - ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %" POINTERSIZE "s Type Name \n", - "Address", "Domain", "MT"); - ExtOut("------------------------------------------------------------------------------\n"); - - if (!g_snapshot.Build()) - { - ExtOut("Unable to build snapshot of the garbage collector state\n"); - return E_FAIL; - } - - PrintRuntimeTypeArgs pargs; - ZeroMemory(&pargs, sizeof(PrintRuntimeTypeArgs)); - - try - { - GCHeapsTraverse(PrintRuntimeTypes, (LPVOID)&pargs); - } - catch(const sos::Exception &e) - { - ExtOut("%s\n", e.what()); - return E_FAIL; - } - - return Status; + return ExecuteCommand("dumpruntimetypes", args); } namespace sos @@ -3823,65 +3718,10 @@ DECLARE_API(AnalyzeOOM) DECLARE_API(VerifyObj) { - INIT_API(); + INIT_API_EXT(); MINIDUMP_NOT_SUPPORTED(); - TADDR taddrObj = 0; - TADDR taddrMT; - size_t objSize; - - BOOL bValid = FALSE; - BOOL dml = FALSE; - - CMDOption option[] = - { // name, vptr, type, hasValue - {"/d", &dml, COBOOL, FALSE}, - }; - CMDValue arg[] = - { // vptr, type - {&taddrObj, COHEX} - }; - size_t nArg; - if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg)) - { - return Status; - } - - EnableDMLHolder dmlHolder(dml); - BOOL bContainsPointers; - - if (FAILED(GetMTOfObject(taddrObj, &taddrMT)) || - !GetSizeEfficient(taddrObj, taddrMT, FALSE, objSize, bContainsPointers)) - { - ExtOut("object %#p does not have valid method table\n", SOS_PTR(taddrObj)); - goto Exit; - } - - // we need to build g_snapshot as it is later used in GetGeneration - if (!g_snapshot.Build()) - { - ExtOut("Unable to build snapshot of the garbage collector state\n"); - goto Exit; - } - - try - { - GCHeapDetails *pheapDetails = g_snapshot.GetHeap(taddrObj); - bValid = VerifyObject(*pheapDetails, taddrObj, taddrMT, objSize, TRUE); - } - catch(const sos::Exception &e) - { - ExtOut("%s\n", e.what()); - return E_FAIL; - } - -Exit: - if (bValid) - { - ExtOut("object %#p is a valid object\n", SOS_PTR(taddrObj)); - } - - return Status; + return ExecuteCommand("verifyobj", args); } DECLARE_API(ListNearObj) diff --git a/src/SOS/Strike/util.h b/src/SOS/Strike/util.h index 6400aafa50..9fd90c7ba0 100644 --- a/src/SOS/Strike/util.h +++ b/src/SOS/Strike/util.h @@ -2043,10 +2043,6 @@ HRESULT GetModuleFromAddress(___in CLRDATA_ADDRESS peAddress, ___out IXCLRDataMo void GetInfoFromName(DWORD_PTR ModuleAddr, const char* name, mdTypeDef* retMdTypeDef=NULL); void GetInfoFromModule (DWORD_PTR ModuleAddr, ULONG token, DWORD_PTR *ret=NULL); - -typedef void (*VISITGCHEAPFUNC)(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable,LPVOID token); -BOOL GCHeapsTraverse(VISITGCHEAPFUNC pFunc, LPVOID token, BOOL verify=true); - ///////////////////////////////////////////////////////////////////////////////////////////////////////// struct strobjInfo @@ -2080,11 +2076,6 @@ void GatherOneHeapFinalization(DacpGcHeapDetails& heapDetails, HeapStat *stat, B CLRDATA_ADDRESS GetAppDomainForMT(CLRDATA_ADDRESS mtPtr); CLRDATA_ADDRESS GetAppDomain(CLRDATA_ADDRESS objPtr); -BOOL VerifyObject(const GCHeapDetails &heap, const DacpHeapSegmentData &seg, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, - BOOL bVerifyMember); -BOOL VerifyObject(const GCHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr, size_t objSize, - BOOL bVerifyMember); - BOOL IsMTForFreeObj(DWORD_PTR pMT); void DumpStackObjectsHelper (TADDR StackTop, TADDR StackBottom, BOOL verifyFields); diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 24b5a6e654..a5dbbbefc5 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -177,7 +177,7 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("dumpmodule", new sosCommand("DumpModule"), "Displays information about a EE module structure at the specified address."); g_services->AddCommand("dumpmt", new sosCommand("DumpMT"), "Displays information about a method table at the specified address."); g_services->AddCommand("dumpobj", new sosCommand("DumpObj"), "Displays info about an object at the specified address."); - g_services->AddCommand("dumpruntimetypes", new sosCommand("DumpRuntimeTypes"), "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too."); + g_services->AddManagedCommand("dumpruntimetypes", "Finds all System.RuntimeType objects in the GC heap and prints the type name and MethodTable they refer too."); g_services->AddCommand("dumpsig", new sosCommand("DumpSig"), "Dumps the signature of a method or field specified by ' '."); g_services->AddCommand("dumpsigelem", new sosCommand("DumpSigElem"), "Dumps a single element of a signature object."); g_services->AddCommand("dumpstack", new sosCommand("DumpStack"), "Displays a native and managed stack trace."); @@ -223,6 +223,6 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("threadstate", new sosCommand("ThreadState"), "Pretty prints the meaning of a threads state."); g_services->AddCommand("token2ee", new sosCommand("token2ee"), "Displays the MethodTable structure and MethodDesc structure for the specified token and module."); g_services->AddManagedCommand("verifyheap", "Checks the GC heap for signs of corruption."); - g_services->AddCommand("verifyobj", new sosCommand("VerifyObj"), "Checks the object that is passed as an argument for signs of corruption."); + g_services->AddManagedCommand("verifyobj", "Checks the object that is passed as an argument for signs of corruption."); return true; }