<Uri>https://github.com/dotnet/symstore</Uri>
<Sha>e09f81a0b38786cb20f66b589a8b88b6997a62da</Sha>
</Dependency>
- <Dependency Name="Microsoft.Diagnostics.Runtime" Version="3.0.0-beta.23210.1">
+ <Dependency Name="Microsoft.Diagnostics.Runtime" Version="3.0.0-beta.23212.1">
<Uri>https://github.com/microsoft/clrmd</Uri>
- <Sha>677f1b9c6fa27b1d2988eac9d51c8a5ea8698fdd</Sha>
+ <Sha>d68eb893272971726b08473935d2c7d088d1db5c</Sha>
</Dependency>
- <Dependency Name="Microsoft.Diagnostics.Runtime.Utilities" Version="3.0.0-beta.23210.1">
+ <Dependency Name="Microsoft.Diagnostics.Runtime.Utilities" Version="3.0.0-beta.23212.1">
<Uri>https://github.com/microsoft/clrmd</Uri>
- <Sha>677f1b9c6fa27b1d2988eac9d51c8a5ea8698fdd</Sha>
+ <Sha>d68eb893272971726b08473935d2c7d088d1db5c</Sha>
</Dependency>
</ProductDependencies>
<ToolsetDependencies>
<SystemReflectionMetadataVersion>5.0.0</SystemReflectionMetadataVersion>
<!-- Other libs -->
<MicrosoftBclAsyncInterfacesVersion>6.0.0</MicrosoftBclAsyncInterfacesVersion>
- <MicrosoftDiagnosticsRuntimeVersion>3.0.0-beta.23210.1</MicrosoftDiagnosticsRuntimeVersion>
+ <MicrosoftDiagnosticsRuntimeVersion>3.0.0-beta.23212.1</MicrosoftDiagnosticsRuntimeVersion>
<MicrosoftDiaSymReaderNativePackageVersion>16.9.0-beta1.21055.5</MicrosoftDiaSymReaderNativePackageVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>3.0.7</MicrosoftDiagnosticsTracingTraceEventVersion>
<MicrosoftExtensionsLoggingVersion>6.0.0</MicrosoftExtensionsLoggingVersion>
bool IMemoryReader.Read<T>(ulong address, out T value)
{
- Span<byte> buffer = stackalloc byte[Marshal.SizeOf<T>()];
+ Span<byte> buffer = stackalloc byte[Unsafe.SizeOf<T>()];
if (((IMemoryReader)this).Read(address, buffer) == buffer.Length)
{
value = Unsafe.As<byte, T>(ref MemoryMarshal.GetReference(buffer));
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "threadpool", Help = "Displays info about the runtime thread pool.")]
+ public sealed class ThreadPoolCommand : CommandBase
+ {
+ [ServiceImport]
+ public ClrRuntime Runtime { get; set; }
+
+ [Option(Name = "-ti", Help = "Print the hill climbing log.", Aliases = new string[] { "-hc" })]
+ public bool PrintHillClimbingLog { get; set; }
+
+ [Option(Name = "-wi", Help = "Print all work items that are queued.")]
+ public bool PrintWorkItems { get; set; }
+
+ public override void Invoke()
+ {
+ // Runtime.ThreadPool shouldn't be null unless there was a problem with the dump.
+ ClrThreadPool threadPool = Runtime.ThreadPool;
+ if (threadPool is null)
+ {
+ Console.WriteLineError("Failed to obtain ThreadPool data.");
+ }
+ else
+ {
+ TableOutput output = new(Console, (17, ""));
+ output.WriteRow("CPU utilization:", $"{threadPool.CpuUtilization}%");
+ output.WriteRow("Workers Total:", threadPool.ActiveWorkerThreads + threadPool.IdleWorkerThreads + threadPool.RetiredWorkerThreads);
+ output.WriteRow("Workers Running:", threadPool.ActiveWorkerThreads);
+ output.WriteRow("Workers Idle:", threadPool.IdleWorkerThreads);
+ output.WriteRow("Worker Min Limit:", threadPool.MinThreads);
+ output.WriteRow("Worker Max Limit:", threadPool.MaxThreads);
+ Console.WriteLine();
+
+ ClrType threadPoolType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPool");
+ ClrStaticField usePortableIOField = threadPoolType?.GetStaticFieldByName("UsePortableThreadPoolForIO");
+
+ // Desktop CLR work items.
+ if (PrintWorkItems)
+ {
+ LegacyThreadPoolWorkRequest[] requests = threadPool.EnumerateLegacyWorkRequests().ToArray();
+ if (requests.Length > 0)
+ {
+ Console.WriteLine($"Work Request in Queue: {requests.Length:n0}");
+ foreach (LegacyThreadPoolWorkRequest request in requests)
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+
+ if (request.IsAsyncTimerCallback)
+ {
+ Console.WriteLine($" AsyncTimerCallbackCompletion TimerInfo@{request.Context:x}");
+ }
+ else
+ {
+ Console.WriteLine($" Unknown Function: {request.Function:x} Context: {request.Context:x}");
+ }
+ }
+ }
+ }
+
+ // We will assume that if UsePortableThreadPoolForIO field is deleted from ThreadPool then we are always
+ // using C# version.
+ bool usingPortableCompletionPorts = threadPool.Portable && (usePortableIOField is null || usePortableIOField.Read<bool>(usePortableIOField.Type.Module.AppDomain));
+ if (!usingPortableCompletionPorts)
+ {
+ output = new(Console, (17, ""));
+ output.WriteRow("Completion Total:", threadPool.TotalCompletionPorts);
+ output.WriteRow("Completion Free:", threadPool.FreeCompletionPorts);
+ output.WriteRow("Completion MaxFree:", threadPool.MaxFreeCompletionPorts);
+ output.WriteRow("Completion Current Limit:", threadPool.CompletionPortCurrentLimit);
+ output.WriteRow("Completion Min Limit:", threadPool.MinCompletionPorts);
+ output.WriteRow("Completion Max Limit:", threadPool.MaxCompletionPorts);
+ Console.WriteLine();
+ }
+
+ if (PrintHillClimbingLog)
+ {
+ HillClimbingLogEntry[] hcl = threadPool.EnumerateHillClimbingLog().ToArray();
+ if (hcl.Length > 0)
+ {
+ output = new(Console, (10, ""), (19, ""), (12, "n0"), (12, "n0"));
+
+ Console.WriteLine("Hill Climbing Log:");
+ output.WriteRow("Time", "Transition", "#New Threads", "#Samples", "Throughput");
+
+ int end = hcl.Last().TickCount;
+ foreach (HillClimbingLogEntry entry in hcl)
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+ output.WriteRow($"{(entry.TickCount - end)/1000.0:0.00}", entry.StateOrTransition, entry.NewControlSetting, entry.LastHistoryCount, $"{entry.LastHistoryMean:0.00}");
+ }
+
+ Console.WriteLine();
+ }
+ }
+ }
+
+ // We can print managed work items even if we failed to request the ThreadPool.
+ if (PrintWorkItems && (threadPool is null || threadPool.Portable))
+ {
+ DumpWorkItems();
+ }
+ }
+
+ private void DumpWorkItems()
+ {
+ TableOutput output = null;
+
+ ClrType workQueueType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPoolWorkQueue");
+ ClrType workStealingQueueType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPoolWorkQueue+WorkStealingQueue");
+
+ foreach (ClrObject obj in Runtime.Heap.EnumerateObjects())
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+
+ if (obj.Type == workQueueType)
+ {
+ if (obj.TryReadObjectField("highPriorityWorkItems", out ClrObject workItems))
+ {
+ foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
+ {
+ WriteEntry(ref output, entry, isHighPri: true);
+ }
+ }
+
+ if (obj.TryReadObjectField("workItems", out workItems))
+ {
+ foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
+ {
+ WriteEntry(ref output, entry, isHighPri: false);
+ }
+ }
+
+ if (obj.Type.Fields.Any(r => r.Name == "_assignableWorkItems"))
+ {
+ if (obj.TryReadObjectField("_assignableWorkItems", out workItems))
+ {
+ foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
+ {
+ WriteEntry(ref output, entry, isHighPri: false);
+ }
+ }
+ }
+ }
+ else if (obj.Type == workStealingQueueType)
+ {
+ if (obj.TryReadObjectField("m_array", out ClrObject m_array) && m_array.IsValid && !m_array.IsNull)
+ {
+ ClrArray arrayView = m_array.AsArray();
+ int len = Math.Min(8192, arrayView.Length); // ensure a sensible max in case we have heap corruption
+
+ nuint[] buffer = arrayView.ReadValues<nuint>(0, len);
+ if (buffer != null)
+ {
+ for (int i = 0; i < len; i++)
+ {
+ if (buffer[i] != 0)
+ {
+ ClrObject entry = Runtime.Heap.GetObject(buffer[i]);
+ if (entry.IsValid && !entry.IsNull)
+ {
+ WriteEntry(ref output, entry, isHighPri: false);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void WriteEntry(ref TableOutput output, ClrObject entry, bool isHighPri)
+ {
+ if (output is null)
+ {
+ output = new(Console, (17, ""), (16, "x12"))
+ {
+ AlignLeft = true,
+ };
+
+ output.WriteRow("Queue", "Object", "Type");
+ }
+
+ output.WriteRow(isHighPri ? "[Global high-pri]" : "[Global]", new DmlDumpObj(entry), entry.Type?.Name);
+ if (entry.IsDelegate)
+ {
+ ClrDelegate del = entry.AsDelegate();
+ ClrDelegateTarget target = del.GetDelegateTarget();
+ if (target is not null)
+ {
+ Console.WriteLine($" => {target.TargetObject.Address:x} {target.Method.Name}");
+ }
+ }
+ }
+
+ private IEnumerable<ClrObject> EnumerateConcurrentQueue(ClrObject concurrentQueue)
+ {
+ if (!concurrentQueue.IsValid || concurrentQueue.IsNull)
+ {
+ yield break;
+ }
+
+ if (concurrentQueue.TryReadObjectField("_head", out ClrObject curr))
+ {
+ while (curr.IsValid && !curr.IsNull)
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+
+ if (curr.TryReadObjectField("_slots", out ClrObject slots) && slots.IsValid && slots.IsArray)
+ {
+ ClrArray slotsArray = slots.AsArray();
+ for (int i = 0; i < slotsArray.Length; i++)
+ {
+ Console.CancellationToken.ThrowIfCancellationRequested();
+
+ ClrObject item = slotsArray.GetStructValue(i).ReadObjectField("Item");
+ if (item.IsValid && !item.IsNull)
+ {
+ yield return item;
+ }
+ }
+ }
+
+ if (!curr.TryReadObjectField("_nextSegment", out curr))
+ {
+ Console.WriteLineError($"Error: Type '{slots.Type?.Name}' does not contain a '_nextSegment' field.");
+ break;
+ }
+ }
+ }
+ }
+ }
+}
[Command(Name = "printexception", DefaultOptions = "PrintException", Aliases = new string[] { "pe" }, Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")]
[Command(Name = "soshelp", DefaultOptions = "Help", Help = "Displays help for a specific SOS command.")]
[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 = "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.")]
return (size_t)stInfo.m_StringLength;
}
- const TADDR GCHeap::HeapStart = 0;
- const TADDR GCHeap::HeapEnd = ~0;
-
- ObjectIterator::ObjectIterator(const GCHeapDetails *heap, int numHeaps, TADDR start, TADDR stop)
- : bLarge(false), bPinned(false), mCurrObj(0), mLastObj(0), mStart(start), mEnd(stop), mSegmentEnd(0), mHeaps(heap),
- mNumHeaps(numHeaps), mCurrHeap(0), mCurrRegionGen(0)
- {
- mAllocInfo.Init();
- SOS_Assert(numHeaps > 0);
-
- TADDR segStart;
- if (heap->has_regions)
- {
- // with regions, we have a null terminated list for each generation
- segStart = TO_TADDR(mHeaps[0].generation_table[mCurrRegionGen].start_segment);
- }
- else
- {
- segStart = TO_TADDR(mHeaps[0].generation_table[GetMaxGeneration()].start_segment);
- }
- if (FAILED(mSegment.Request(g_sos, segStart, mHeaps[0].original_heap_details)))
- {
- sos::Throw<DataRead>("Could not request segment data at %p.", segStart);
- }
-
- mCurrObj = mStart < TO_TADDR(mSegment.mem) ? TO_TADDR(mSegment.mem) : mStart;
- mSegmentEnd = TO_TADDR(mSegment.highAllocMark);
-
- TryAlignToObjectInRange();
- }
-
- bool ObjectIterator::TryMoveNextSegment()
- {
- CheckInterrupt();
-
- if (mCurrHeap >= mNumHeaps)
- {
- return false;
- }
-
- TADDR next = TO_TADDR(mSegment.next);
- if (next == NULL)
- {
- if (mHeaps[mCurrHeap].has_regions)
- {
- mCurrRegionGen++;
- if ((mCurrRegionGen > GetMaxGeneration() + 2) ||
- (mCurrRegionGen > GetMaxGeneration() + 1 && !mHeaps[mCurrHeap].has_poh))
- {
- mCurrHeap++;
- if (mCurrHeap == mNumHeaps)
- {
- return false;
- }
- mCurrRegionGen = 0;
- }
- next = TO_TADDR(mHeaps[mCurrHeap].generation_table[mCurrRegionGen].start_segment);
- }
- else if (bPinned || (bLarge && !mHeaps[mCurrHeap].has_poh))
- {
- mCurrHeap++;
- if (mCurrHeap == mNumHeaps)
- {
- return false;
- }
-
- bPinned = false;
- bLarge = false;
- next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration()].start_segment);
- }
- else if (bLarge)
- {
- bLarge = false;
- bPinned = true;
- next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration() + 2].start_segment);
- }
- else
- {
- bLarge = true;
- next = TO_TADDR(mHeaps[mCurrHeap].generation_table[GetMaxGeneration() + 1].start_segment);
- }
- }
-
- SOS_Assert(next != NULL);
- if (FAILED(mSegment.Request(g_sos, next, mHeaps[mCurrHeap].original_heap_details)))
- {
- sos::Throw<DataRead>("Failed to request segment data at %p.", next);
- }
-
- mLastObj = 0;
- mCurrObj = mStart < TO_TADDR(mSegment.mem) ? TO_TADDR(mSegment.mem) : mStart;
- mSegmentEnd = TO_TADDR(mSegment.highAllocMark);
- return true;
- }
-
- bool ObjectIterator::TryMoveToObjectInNextSegmentInRange()
- {
- if (TryMoveNextSegment())
- {
- return TryAlignToObjectInRange();
- }
-
- return false;
- }
-
- bool ObjectIterator::TryAlignToObjectInRange()
- {
- CheckInterrupt();
- while (!MemOverlap(mStart, mEnd, TO_TADDR(mSegment.mem), mSegmentEnd))
- {
- CheckInterrupt();
- if (!TryMoveNextSegment())
- {
- return false;
- }
- }
-
- // At this point we know that the current segment contains objects in
- // the correct range. However, there's no telling if the user gave us
- // a starting address that corresponds to an object. If mStart is a
- // valid object, then we'll just start there. If it's not we'll need
- // to walk the segment from the beginning to find the first aligned
- // object on or after mStart.
- if (mCurrObj == mStart && !Object::IsValid(mStart))
- {
- // It's possible mCurrObj will equal mStart after this. That's fine.
- // It means that the starting object is corrupt (and we'll figure
- // that when the user calls GetNext), or IsValid was wrong.
- mLastObj = 0;
- mCurrObj = TO_TADDR(mSegment.mem);
- while (mCurrObj < mStart)
- MoveToNextObject();
- }
-
- return true;
- }
-
-
-
- const Object &ObjectIterator::operator*() const
- {
- AssertSanity();
- return mCurrObj;
- }
-
-
- const Object *ObjectIterator::operator->() const
- {
- AssertSanity();
- return &mCurrObj;
- }
-
- //Object ObjectIterator::GetNext()
- const ObjectIterator &ObjectIterator::operator++()
- {
- CheckInterrupt();
-
- // Assert we aren't done walking the heap.
- SOS_Assert(*this);
- AssertSanity();
-
- MoveToNextObject();
- return *this;
- }
-
- void ObjectIterator::MoveToNextObjectCarefully()
- {
- CheckInterrupt();
-
- SOS_Assert(*this);
- AssertSanity();
-
- // Move to NextObject won't generally throw unless it fails to request the
- // MethodTable of the object. At which point we won't know how large the
- // current object is, nor how to move past it. In this case we'll simply
- // move to the next segment if possible to continue iterating from there.
- try
- {
- MoveToNextObject();
- }
- catch(const sos::Exception &)
- {
- TryMoveToObjectInNextSegmentInRange();
- }
- }
-
- void ObjectIterator::AssertSanity() const
- {
- // Assert that we are in a sane state. Function which call this assume two things:
- // 1. That the current object is within the segment bounds.
- // 2. That the current object is within the requested memory range.
- SOS_Assert(mCurrObj >= TO_TADDR(mSegment.mem));
- SOS_Assert(mCurrObj <= TO_TADDR(mSegmentEnd - Align(min_obj_size)));
-
- SOS_Assert(mCurrObj >= mStart);
- SOS_Assert(mCurrObj <= mEnd);
- }
-
- void ObjectIterator::MoveToNextObject()
- {
- CheckInterrupt();
-
- // Object::GetSize can be unaligned, so we must align it ourselves.
- size_t size = (bLarge || bPinned) ? AlignLarge(mCurrObj.GetSize()) : Align(mCurrObj.GetSize());
-
- mLastObj = mCurrObj;
- mCurrObj = mCurrObj.GetAddress() + size;
-
- if (!bLarge)
- {
- // Is this the end of an allocation context? We need to know this because there can be
- // allocated memory at the end of an allocation context that doesn't yet contain any objects.
- // This happens because we actually allocate a minimum amount of memory (the allocation quantum)
- // whenever we need to get more memory. Typically, a single allocation request won't fill this
- // block, so we'll fulfill subsequent requests out of the remainder of the block until it's
- // depleted.
- int i;
- for (i = 0; i < mAllocInfo.num; i ++)
- {
- if (mCurrObj == TO_TADDR(mAllocInfo.array[i].alloc_ptr)) // end of objects in this context
- {
- // Set mCurrObj to point after the context (alloc_limit is the end of the allocation context).
- mCurrObj = TO_TADDR(mAllocInfo.array[i].alloc_limit) + Align(min_obj_size);
- break;
- }
- }
-
- // We also need to look at the gen0 alloc context.
- if (mCurrObj == TO_TADDR(mHeaps[mCurrHeap].generation_table[0].allocContextPtr))
- mCurrObj = TO_TADDR(mHeaps[mCurrHeap].generation_table[0].allocContextLimit) + Align(min_obj_size);
- }
-
- if (mCurrObj > mEnd || mCurrObj >= mSegmentEnd)
- {
- TryMoveToObjectInNextSegmentInRange();
- }
- }
-
- SyncBlkIterator::SyncBlkIterator()
- : mCurr(1), mTotal(0)
- {
- // If DacpSyncBlockData::Request fails with the call "1", then it means
- // there are no SyncBlocks in the process.
- DacpSyncBlockData syncBlockData;
- if (SUCCEEDED(syncBlockData.Request(g_sos, 1)))
- {
- mTotal = syncBlockData.SyncBlockCount;
- mSyncBlk = mCurr;
- }
- }
-
- GCHeap::GCHeap()
- {
- if (FAILED(mHeapData.Request(g_sos)))
- {
- sos::Throw<DataRead>("Failed to request GC heap data.");
- }
-
- if (mHeapData.bServerMode)
- {
- mNumHeaps = mHeapData.HeapCount;
- DWORD dwAllocSize = 0;
- if (!ClrSafeInt<DWORD>::multiply(sizeof(CLRDATA_ADDRESS), mNumHeaps, dwAllocSize))
- {
- sos::Throw<Exception>("Failed to get GCHeaps: Integer overflow.");
- }
-
- CLRDATA_ADDRESS *heapAddrs = (CLRDATA_ADDRESS*)alloca(dwAllocSize);
- if (FAILED(g_sos->GetGCHeapList(mNumHeaps, heapAddrs, NULL)))
- {
- sos::Throw<DataRead>("Failed to get GCHeaps.");
- }
-
- mHeaps = new GCHeapDetails[mNumHeaps];
-
- for (int i = 0; i < mNumHeaps; i++)
- {
- DacpGcHeapDetails dacHeapDetails;
- if (FAILED(dacHeapDetails.Request(g_sos, heapAddrs[i])))
- {
- sos::Throw<DataRead>("Failed to get GC heap details at %p.", heapAddrs[i]);
- }
-
- mHeaps[i].Set(dacHeapDetails, heapAddrs[i]);
- }
- }
- else
- {
- mHeaps = new GCHeapDetails[1];
- mNumHeaps = 1;
-
- DacpGcHeapDetails dacGCDetails;
- if (FAILED(dacGCDetails.Request(g_sos)))
- {
- sos::Throw<DataRead>("Failed to request GC details data.");
- }
-
- mHeaps[0].Set(dacGCDetails);
- }
- }
-
- GCHeap::~GCHeap()
- {
- delete [] mHeaps;
- }
-
- ObjectIterator GCHeap::WalkHeap(TADDR start, TADDR stop) const
- {
- return ObjectIterator(mHeaps, mNumHeaps, start, stop);
- }
-
- bool GCHeap::AreGCStructuresValid() const
- {
- return mHeapData.bGcStructuresValid != FALSE;
- }
-
// SyncBlk class
SyncBlk::SyncBlk()
: mIndex(0)
namespace sos
{
- class GCHeap;
-
/* The base SOS Exception. Note that most commands should not attempt to be
* resilient to exceptions thrown by most functions here. Instead a top level
* try/catch at the beginning of the command which prints out the exception's
mutable WCHAR *mTypeName;
};
- /* 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)
- * itr->SomeObjectMethod();
- */
- class ObjectIterator
- {
- friend class GCHeap;
- public:
-
- /* Returns the next object in the GCHeap. Note that you must ensure
- * that there are more objects to walk before calling this function by
- * checking "if (iterator)". If this function throws an exception,
- * the the iterator is invalid, and should no longer be used to walk
- * the heap. This should generally only happen if we cannot read the
- * MethodTable of the object to move to the next object.
- * Throws:
- * DataRead
- */
- const ObjectIterator &operator++();
-
- /* Dereference operator. This allows you to take a reference to the
- * current object. Note the lifetime of this reference is valid for
- * either the lifetime of the iterator or until you call operator++,
- * whichever is shorter. For example.
- * void Foo(const Object ¶m);
- * void Bar(const ObjectIterator &itr)
- * {
- * Foo(*itr);
- * }
- */
- const Object &operator*() const;
-
- /* Returns a pointer to the current Object to call members on it.
- * The usage pattern for the iterator is to simply use operator->
- * to call methods on the Object it points to without taking a
- * direct reference to the underlying Object if at all possible.
- */
- const Object *operator->() const;
-
- /* Returns false when the iterator has reached the end of the managed
- * heap.
- */
- inline operator void *() const
- {
- return (void*)(SIZE_T)(mCurrHeap == mNumHeaps ? 0 : 1);
- }
-
- /* Do not use.
- * TODO: Replace this functionality with int Object::GetGeneration().
- */
- bool IsCurrObjectOnLOH() const
- {
- SOS_Assert(*this);
- return bLarge;
- }
-
- /* 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
- * at the current object, you can use MoveToNextObjectCarefully instead of
- * ObjectIterator++ to attempt to keep reading from the heap. If possible,
- * this function attempts to move to the next object in the same segment,
- * but if that's not possible then it skips to the next segment and
- * continues from there.
- * Note:
- * This function can throw, and if it does then the iterator is no longer
- * in a valid state. No further attempts to move to the next object will
- * be possible.
- * Throws:
- * DataRead - if the heap is corrupted and it's not possible to continue
- * walking the heap
- */
- void MoveToNextObjectCarefully();
-
- private:
- ObjectIterator(const GCHeapDetails *heap, int numHeaps, TADDR start, TADDR stop);
-
- void AssertSanity() const;
-
- /*
- This function moves to the next segment/region without checking any restrictions
- on the range. Returns true if it was able to move to a new segment/region.
- */
- bool TryMoveNextSegment();
-
- /*
- Aligns the iterator to the object that falls in the requested range, moving to
- the next segment/region as necessary. The iterator state doesn't change if the
- current object already lies in the requested range. Returns true if aligning
- to such an object was possible.
- */
- bool TryAlignToObjectInRange();
-
- /*
- Moves to the next segment/region that contains an object in the requested
- range and align it to such object. This operation always moves the iterator.
- Returns false if no such move was possible.
- */
- bool TryMoveToObjectInNextSegmentInRange();
- void MoveToNextObject();
-
- private:
- DacpHeapSegmentData mSegment;
- bool bLarge;
- bool bPinned;
- Object mCurrObj;
- TADDR mLastObj, mStart, mEnd, mSegmentEnd;
- AllocInfo mAllocInfo;
- const GCHeapDetails *mHeaps;
- int mNumHeaps;
- int mCurrHeap;
- unsigned mCurrRegionGen;
- };
-
/* Reprensents an entry in the sync block table.
*/
class SyncBlk
SyncBlk mSyncBlk;
};
- /* An class which contains information about the GCHeap.
- */
- class GCHeap
- {
- public:
- static const TADDR HeapStart; // A constant signifying the start of the GC heap.
- static const TADDR HeapEnd; // A constant signifying the end of the GC heap.
-
- public:
- /* Constructor.
- * Throws:
- * DataRead
- */
- GCHeap();
-
- ~GCHeap();
-
- /* Returns an ObjectIterator which allows you to walk the objects on the managed heap.
- * This ObjectIterator is valid for the duration of the GCHeap's lifetime. Note that
- * if you specify an address at which you wish to start walking the heap it need
- * not point directly to a managed object. However, if it does not, WalkHeap
- * will need to walk the segment that address resides in to find the first object
- * after that address, and if it encounters any heap corruption along the way,
- * it may be impossible to walk the heap from the address specified.
- *
- * Params:
- * start - The starting address at which you want to start walking the heap.
- * This need not point directly to an object on the heap.
- * end - The ending address at which you want to stop walking the heap. This
- * need not point directly to an object on the heap.
- * validate - Whether or not you wish to validate the GC heap as you walk it.
- * Throws:
- * DataRead
- */
- ObjectIterator WalkHeap(TADDR start = HeapStart, TADDR stop = HeapEnd) const;
-
- /* Returns true if the GC Heap structures are in a valid state for traversal.
- * Returns false if not (e.g. if we are in the middle of a relocation).
- */
- bool AreGCStructuresValid() const;
-
- private:
- GCHeapDetails *mHeaps;
- DacpGcHeapData mHeapData;
- int mNumHeaps;
- };
-
// convenience functions
/* A temporary wrapper function for Object::IsValid. There are too many locations
* in SOS which need to use IsObject but have a wide variety of internal
\**********************************************************************/
DECLARE_API(ThreadPool)
{
- INIT_API();
+ INIT_API_EXT();
MINIDUMP_NOT_SUPPORTED();
- BOOL doHCDump = FALSE, doWorkItemDump = FALSE, dml = FALSE;
- BOOL mustBePortableThreadPool = FALSE;
-
- CMDOption option[] =
- { // name, vptr, type, hasValue
- {"-ti", &doHCDump, COBOOL, FALSE},
- {"-wi", &doWorkItemDump, COBOOL, FALSE},
- {"/d", &dml, COBOOL, FALSE},
- };
-
- if (!GetCMDOption(args, option, ARRAY_SIZE(option), NULL, 0, NULL))
- {
- return E_FAIL;
- }
-
- EnableDMLHolder dmlHolder(dml);
-
- DacpThreadpoolData threadpool;
- Status = threadpool.Request(g_sos);
- if (Status == E_NOTIMPL)
- {
- mustBePortableThreadPool = TRUE;
- }
- else if (Status != S_OK)
- {
- ExtOut(" %s\n", "Failed to request ThreadpoolMgr information");
- return FAILED(Status) ? Status : E_FAIL;
- }
-
- DWORD_PTR corelibModule;
- {
- int numModule;
- ArrayHolder<DWORD_PTR> moduleList = ModuleFromName(const_cast<LPSTR>("System.Private.CoreLib.dll"), &numModule);
- if (moduleList == NULL || numModule != 1)
- {
- ExtOut(" %s\n", "Failed to find System.Private.CoreLib.dll");
- return E_FAIL;
- }
- corelibModule = moduleList[0];
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // Check whether the portable thread pool is being used and fill in the thread pool data
-
- UINT64 ui64Value = 0;
- DacpObjectData vPortableTpHcLogArray;
- int portableTpHcLogEntry_tickCountOffset = 0;
- int portableTpHcLogEntry_stateOrTransitionOffset = 0;
- int portableTpHcLogEntry_newControlSettingOffset = 0;
- int portableTpHcLogEntry_lastHistoryCountOffset = 0;
- int portableTpHcLogEntry_lastHistoryMeanOffset = 0;
- do // while (false)
- {
- if (!mustBePortableThreadPool)
- {
- // Determine if the portable thread pool is enabled
- if (FAILED(
- GetNonSharedStaticFieldValueFromName(
- &ui64Value,
- corelibModule,
- "System.Threading.ThreadPool",
- W("UsePortableThreadPool"),
- ELEMENT_TYPE_BOOLEAN)) ||
- ui64Value == 0)
- {
- // The type was not loaded yet, or the static field was not found, etc. For now assume that the portable thread pool
- // is not being used.
- break;
- }
- }
-
- // Get the thread pool instance
- if (FAILED(
- GetNonSharedStaticFieldValueFromName(
- &ui64Value,
- corelibModule,
- "System.Threading.PortableThreadPool",
- W("ThreadPoolInstance"),
- ELEMENT_TYPE_CLASS)) ||
- ui64Value == 0)
- {
- // The type was not loaded yet, or the static field was not found, etc. For now assume that the portable thread pool
- // is not being used.
- break;
- }
- CLRDATA_ADDRESS cdaTpInstance = TO_CDADDR(ui64Value);
-
- // Get the thread pool method table
- CLRDATA_ADDRESS cdaTpMethodTable;
- {
- TADDR tpMethodTableAddr = NULL;
- if (FAILED(GetMTOfObject(TO_TADDR(cdaTpInstance), &tpMethodTableAddr)))
- {
- break;
- }
- cdaTpMethodTable = TO_CDADDR(tpMethodTableAddr);
- }
-
- DWORD_PTR ptrValue = 0;
- INT32 i32Value = 0;
- INT16 i16Value = 0;
- int offset = 0;
-
- // Populate fields of the thread pool with simple types
- {
- offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_cpuUtilization"));
- if (offset <= 0 || FAILED(MOVE(i32Value, cdaTpInstance + offset)))
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool._cpuUtilization");
- break;
- }
- threadpool.cpuUtilization = i32Value;
-
- offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_minThreads"));
- if (offset <= 0 || FAILED(MOVE(i16Value, cdaTpInstance + offset)))
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool._minThreads");
- break;
- }
- threadpool.MinLimitTotalWorkerThreads = i16Value;
-
- offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_maxThreads"));
- if (offset <= 0 || FAILED(MOVE(i16Value, cdaTpInstance + offset)))
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool._maxThreads");
- break;
- }
- threadpool.MaxLimitTotalWorkerThreads = i16Value;
- }
-
- // Populate thread counts
- {
- DacpFieldDescData vSeparatedField;
- offset = GetObjFieldOffset(cdaTpInstance, cdaTpMethodTable, W("_separated"), TRUE, &vSeparatedField);
- if (offset <= 0)
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool._separated");
- break;
- }
- int accumulatedOffset = offset;
-
- DacpFieldDescData vCountsField;
- offset = GetValueFieldOffset(vSeparatedField.MTOfType, W("counts"), &vCountsField);
- if (offset < 0)
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool._separated.counts");
- break;
- }
- accumulatedOffset += offset;
-
- offset = GetValueFieldOffset(vCountsField.MTOfType, W("_data"));
- if (offset < 0 || FAILED(MOVE(ui64Value, cdaTpInstance + accumulatedOffset + offset)))
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool._separated.counts._data");
- break;
- }
- UINT64 data = ui64Value;
-
- const UINT8 NumProcessingWorkShift = 0;
- const UINT8 NumExistingThreadsShift = 16;
-
- INT16 numProcessingWork = (INT16)(data >> NumProcessingWorkShift);
- INT16 numExistingThreads = (INT16)(data >> NumExistingThreadsShift);
-
- threadpool.NumIdleWorkerThreads = numExistingThreads - numProcessingWork;
- threadpool.NumWorkingWorkerThreads = numProcessingWork;
- threadpool.NumRetiredWorkerThreads = 0;
- }
-
- // Populate hill climbing log info
- {
- threadpool.HillClimbingLog = 0; // this indicates that the portable thread pool's hill climbing data should be used
- threadpool.HillClimbingLogFirstIndex = 0;
- threadpool.HillClimbingLogSize = 0;
-
- // Get the hill climbing instance
- if (FAILED(
- GetNonSharedStaticFieldValueFromName(
- &ui64Value,
- corelibModule,
- "System.Threading.PortableThreadPool+HillClimbing",
- W("ThreadPoolHillClimber"),
- ELEMENT_TYPE_CLASS)) ||
- ui64Value == 0)
- {
- // The type was not loaded yet, or the static field was not found, etc. For now assume that the hill climber has
- // not been used yet.
- break;
- }
- CLRDATA_ADDRESS cdaTpHcInstance = TO_CDADDR(ui64Value);
-
- // Get the thread pool method table
- CLRDATA_ADDRESS cdaTpHcMethodTable;
- {
- TADDR tpHcMethodTableAddr = NULL;
- if (FAILED(GetMTOfObject(TO_TADDR(cdaTpHcInstance), &tpHcMethodTableAddr)))
- {
- ExtOut(" %s\n", "Failed to get method table for PortableThreadPool.HillClimbing");
- break;
- }
- cdaTpHcMethodTable = TO_CDADDR(tpHcMethodTableAddr);
- }
-
- offset = GetObjFieldOffset(cdaTpHcInstance, cdaTpHcMethodTable, W("_logStart"));
- if (offset <= 0 || FAILED(MOVE(i32Value, cdaTpHcInstance + offset)))
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool.HillClimbing._logStart");
- break;
- }
- int logStart = i32Value;
-
- offset = GetObjFieldOffset(cdaTpHcInstance, cdaTpHcMethodTable, W("_logSize"));
- if (offset <= 0 || FAILED(MOVE(i32Value, cdaTpHcInstance + offset)))
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool.HillClimbing._logSize");
- break;
- }
- int logSize = i32Value;
-
- offset = GetObjFieldOffset(cdaTpHcInstance, cdaTpHcMethodTable, W("_log"));
- if (offset <= 0 || FAILED(MOVE(ptrValue, cdaTpHcInstance + offset)) || ptrValue == 0)
- {
- ExtOut(" %s\n", "Failed to read PortableThreadPool.HillClimbing._log");
- break;
- }
- CLRDATA_ADDRESS cdaTpHcLog = TO_CDADDR(ptrValue);
-
- // Validate the log array
- if (!sos::IsObject(cdaTpHcLog, false) ||
- vPortableTpHcLogArray.Request(g_sos, cdaTpHcLog) != S_OK ||
- vPortableTpHcLogArray.ObjectType != OBJ_ARRAY ||
- vPortableTpHcLogArray.ArrayDataPtr == 0 ||
- vPortableTpHcLogArray.dwComponentSize != sizeof(HillClimbingLogEntry) ||
- vPortableTpHcLogArray.ElementTypeHandle == 0)
- {
- ExtOut(" %s\n", "Failed to validate PortableThreadPool.HillClimbing._log");
- break;
- }
-
- // Get the log entry field offsets
- portableTpHcLogEntry_tickCountOffset =
- GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("tickCount"));
- portableTpHcLogEntry_stateOrTransitionOffset =
- GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("stateOrTransition"));
- portableTpHcLogEntry_newControlSettingOffset =
- GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("newControlSetting"));
- portableTpHcLogEntry_lastHistoryCountOffset =
- GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("lastHistoryCount"));
- portableTpHcLogEntry_lastHistoryMeanOffset =
- GetValueFieldOffset(vPortableTpHcLogArray.ElementTypeHandle, W("lastHistoryMean"));
- if (portableTpHcLogEntry_tickCountOffset < 0 ||
- portableTpHcLogEntry_stateOrTransitionOffset < 0 ||
- portableTpHcLogEntry_newControlSettingOffset < 0 ||
- portableTpHcLogEntry_lastHistoryCountOffset < 0 ||
- portableTpHcLogEntry_lastHistoryMeanOffset < 0)
- {
- ExtOut(" %s\n", "Failed to get a field offset in PortableThreadPool.HillClimbing.LogEntry");
- break;
- }
-
- ExtOut("logStart: %d\n", logStart);
- ExtOut("logSize: %d\n", logSize);
- threadpool.HillClimbingLogFirstIndex = logStart;
- threadpool.HillClimbingLogSize = logSize;
- }
- } while (false);
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- ExtOut ("CPU utilization: %d %s\n", threadpool.cpuUtilization, "%");
- ExtOut ("Worker Thread:");
- ExtOut (" Total: %d", threadpool.NumWorkingWorkerThreads + threadpool.NumIdleWorkerThreads + threadpool.NumRetiredWorkerThreads);
- ExtOut (" Running: %d", threadpool.NumWorkingWorkerThreads);
- ExtOut (" Idle: %d", threadpool.NumIdleWorkerThreads);
- ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalWorkerThreads);
- ExtOut (" MinLimit: %d", threadpool.MinLimitTotalWorkerThreads);
- ExtOut ("\n");
-
- int numWorkRequests = 0;
- CLRDATA_ADDRESS workRequestPtr = threadpool.FirstUnmanagedWorkRequest;
- DacpWorkRequestData workRequestData;
- while (workRequestPtr)
- {
- if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK)
- {
- ExtOut(" Failed to examine a WorkRequest\n");
- return Status;
- }
- numWorkRequests++;
- workRequestPtr = workRequestData.NextWorkRequest;
- }
-
- ExtOut ("Work Request in Queue: %d\n", numWorkRequests);
- workRequestPtr = threadpool.FirstUnmanagedWorkRequest;
- while (workRequestPtr)
- {
- if ((Status = workRequestData.Request(g_sos,workRequestPtr))!=S_OK)
- {
- ExtOut(" Failed to examine a WorkRequest\n");
- return Status;
- }
-
- if (workRequestData.Function == threadpool.AsyncTimerCallbackCompletionFPtr)
- ExtOut (" AsyncTimerCallbackCompletion TimerInfo@%p\n", SOS_PTR(workRequestData.Context));
- else
- ExtOut (" Unknown Function: %p Context: %p\n", SOS_PTR(workRequestData.Function),
- SOS_PTR(workRequestData.Context));
-
- workRequestPtr = workRequestData.NextWorkRequest;
- }
-
- if (doWorkItemDump && g_snapshot.Build())
- {
- // Display a message if the heap isn't verified.
- sos::GCHeap gcheap;
- if (!gcheap.AreGCStructuresValid())
- {
- DisplayInvalidStructuresMessage();
- }
-
- mdTypeDef threadPoolWorkQueueMd, threadPoolWorkStealingQueueMd;
- GetInfoFromName(corelibModule, "System.Threading.ThreadPoolWorkQueue", &threadPoolWorkQueueMd);
- GetInfoFromName(corelibModule, "System.Threading.ThreadPoolWorkQueue+WorkStealingQueue", &threadPoolWorkStealingQueueMd);
-
- // Walk every heap item looking for the global queue and local queues.
- ExtOut("\nQueued work items:\n%" THREAD_POOL_WORK_ITEM_TABLE_QUEUE_WIDTH "s %" POINTERSIZE "s %s\n", "Queue", "Address", "Work Item");
- HeapStat stats;
- for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr)
- {
- DacpMethodTableData mtdata;
- if (mtdata.Request(g_sos, TO_TADDR(itr->GetMT())) != S_OK ||
- mtdata.Module != corelibModule)
- {
- continue;
- }
-
- if (mtdata.cl == threadPoolWorkQueueMd)
- {
- // We found a ThreadPoolWorkQueue (there should be only one, given one AppDomain).
-
- // Enumerate high-priority work items.
- int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("highPriorityWorkItems"));
- if (offset > 0)
- {
- DWORD_PTR workItemsConcurrentQueuePtr;
- MOVE(workItemsConcurrentQueuePtr, itr->GetAddress() + offset);
- if (sos::IsObject(workItemsConcurrentQueuePtr, false))
- {
- // We got the ConcurrentQueue. Enumerate it.
- EnumerateThreadPoolGlobalWorkItemConcurrentQueue(workItemsConcurrentQueuePtr, "[Global high-pri]", &stats);
- }
- }
-
- // Enumerate assignable normal-priority work items.
- offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("_assignableWorkItemQueues"));
- if (offset > 0)
- {
- DWORD_PTR workItemsConcurrentQueueArrayPtr;
- MOVE(workItemsConcurrentQueueArrayPtr, itr->GetAddress() + offset);
- DacpObjectData workItemsConcurrentQueueArray;
- if (workItemsConcurrentQueueArray.Request(g_sos, TO_CDADDR(workItemsConcurrentQueueArrayPtr)) == S_OK &&
- workItemsConcurrentQueueArray.ObjectType == OBJ_ARRAY)
- {
- for (int i = 0; i < workItemsConcurrentQueueArray.dwNumComponents; i++)
- {
- DWORD_PTR workItemsConcurrentQueuePtr;
- MOVE(workItemsConcurrentQueuePtr, workItemsConcurrentQueueArray.ArrayDataPtr + (i * workItemsConcurrentQueueArray.dwComponentSize));
- if (workItemsConcurrentQueuePtr != NULL && sos::IsObject(TO_CDADDR(workItemsConcurrentQueuePtr), false))
- {
- // We got the ConcurrentQueue. Enumerate it.
- EnumerateThreadPoolGlobalWorkItemConcurrentQueue(workItemsConcurrentQueuePtr, "[Global]", &stats);
- }
- }
- }
- }
-
- // Enumerate normal-priority work items.
- offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("workItems"));
- if (offset > 0)
- {
- DWORD_PTR workItemsConcurrentQueuePtr;
- MOVE(workItemsConcurrentQueuePtr, itr->GetAddress() + offset);
- if (sos::IsObject(workItemsConcurrentQueuePtr, false))
- {
- // We got the ConcurrentQueue. Enumerate it.
- EnumerateThreadPoolGlobalWorkItemConcurrentQueue(workItemsConcurrentQueuePtr, "[Global]", &stats);
- }
- }
- }
- else if (mtdata.cl == threadPoolWorkStealingQueueMd)
- {
- // We found a local queue. Get its work items array.
- int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("m_array"));
- if (offset > 0)
- {
- // Walk every element in the array, outputting details on non-null work items.
- DWORD_PTR workItemArrayPtr;
- MOVE(workItemArrayPtr, itr->GetAddress() + offset);
- DacpObjectData workItemArray;
- if (workItemArray.Request(g_sos, TO_CDADDR(workItemArrayPtr)) == S_OK && workItemArray.ObjectType == OBJ_ARRAY)
- {
- for (int i = 0; i < workItemArray.dwNumComponents; i++)
- {
- DWORD_PTR workItemPtr;
- MOVE(workItemPtr, workItemArray.ArrayDataPtr + (i * workItemArray.dwComponentSize));
- 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", DMLObject(itr->GetAddress()), 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");
- }
- }
- }
- }
- }
- }
-
- // Output a summary.
- stats.Sort();
- stats.Print();
- ExtOut("\n");
- }
-
- if (doHCDump)
- {
- ExtOut ("--------------------------------------\n");
- ExtOut ("\nThread Injection History\n");
- if (threadpool.HillClimbingLogSize > 0)
- {
- static char const * const TransitionNames[] =
- {
- "Warmup",
- "Initializing",
- "RandomMove",
- "ClimbingMove",
- "ChangePoint",
- "Stabilizing",
- "Starvation",
- "ThreadTimedOut",
- "CooperativeBlocking",
- "Undefined"
- };
-
- bool usePortableThreadPoolHillClimbingData = threadpool.HillClimbingLog == 0;
- int logCapacity =
- usePortableThreadPoolHillClimbingData
- ? (int)vPortableTpHcLogArray.dwNumComponents
- : HillClimbingLogCapacity;
-
- ExtOut("\n Time Transition New #Threads #Samples Throughput\n");
- DacpHillClimbingLogEntry entry;
-
- // Get the most recent entry first, so we can calculate time offsets
- DWORD endTime;
- int index = (threadpool.HillClimbingLogFirstIndex + threadpool.HillClimbingLogSize - 1) % logCapacity;
- if (usePortableThreadPoolHillClimbingData)
- {
- CLRDATA_ADDRESS entryPtr =
- TO_CDADDR(vPortableTpHcLogArray.ArrayDataPtr + index * sizeof(HillClimbingLogEntry));
- INT32 i32Value = 0;
-
- if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_tickCountOffset)))
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
-
- endTime = i32Value;
- }
- else
- {
- CLRDATA_ADDRESS entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry));
- if ((Status = entry.Request(g_sos, entryPtr)) != S_OK)
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
-
- endTime = entry.TickCount;
- }
-
- for (int i = 0; i < threadpool.HillClimbingLogSize; i++)
- {
- index = (i + threadpool.HillClimbingLogFirstIndex) % logCapacity;
- if (usePortableThreadPoolHillClimbingData)
- {
- CLRDATA_ADDRESS entryPtr =
- TO_CDADDR(vPortableTpHcLogArray.ArrayDataPtr + (index * sizeof(HillClimbingLogEntry)));
- INT32 i32Value = 0;
- float f32Value = 0;
-
- if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_tickCountOffset)))
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
- entry.TickCount = i32Value;
-
- if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_stateOrTransitionOffset)))
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
- entry.Transition = i32Value;
-
- if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_newControlSettingOffset)))
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
- entry.NewControlSetting = i32Value;
-
- if (FAILED(Status = MOVE(i32Value, entryPtr + portableTpHcLogEntry_lastHistoryCountOffset)))
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
- entry.LastHistoryCount = i32Value;
-
- if (FAILED(Status = MOVE(f32Value, entryPtr + portableTpHcLogEntry_lastHistoryMeanOffset)))
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
- entry.LastHistoryMean = f32Value;
- }
- else
- {
- CLRDATA_ADDRESS entryPtr = threadpool.HillClimbingLog + (index * sizeof(HillClimbingLogEntry));
-
- if ((Status = entry.Request(g_sos, entryPtr)) != S_OK)
- {
- ExtOut(" Failed to examine a HillClimbing log entry\n");
- return Status;
- }
- }
-
- ExtOut("%8.2lf %-14s %12d %12d %11.2lf\n",
- (double)(int)(entry.TickCount - endTime) / 1000.0,
- TransitionNames[entry.Transition],
- entry.NewControlSetting,
- entry.LastHistoryCount,
- entry.LastHistoryMean);
- }
- }
- }
-
- ExtOut ("--------------------------------------\n");
- ExtOut ("Number of Timers: %d\n", threadpool.NumTimers);
- ExtOut ("--------------------------------------\n");
-
- // Determine if the portable thread pool is being used for IO. The portable thread pool does not use a separate set of
- // threads for processing IO completions.
- if (FAILED(
- GetNonSharedStaticFieldValueFromName(
- &ui64Value,
- corelibModule,
- "System.Threading.ThreadPool",
- W("UsePortableThreadPoolForIO"),
- ELEMENT_TYPE_BOOLEAN)) ||
- ui64Value == 0)
- {
- ExtOut ("Completion Port Thread:");
- ExtOut ("Total: %d", threadpool.NumCPThreads);
- ExtOut (" Free: %d", threadpool.NumFreeCPThreads);
- ExtOut (" MaxFree: %d", threadpool.MaxFreeCPThreads);
- ExtOut (" CurrentLimit: %d", threadpool.CurrentLimitTotalCPThreads);
- ExtOut (" MaxLimit: %d", threadpool.MaxLimitTotalCPThreads);
- ExtOut (" MinLimit: %d", threadpool.MinLimitTotalCPThreads);
- ExtOut ("\n");
- }
-
- return S_OK;
+ return ExecuteCommand("threadpool", args);
}
DECLARE_API(FindAppDomain)
return;
}
-HRESULT GetNonSharedStaticFieldValueFromName(
- UINT64* pValue,
- DWORD_PTR moduleAddr,
- const char *typeName,
- __in_z LPCWSTR wszFieldName,
- CorElementType fieldType)
-{
- HRESULT hr = S_OK;
-
- mdTypeDef mdType = 0;
- GetInfoFromName(moduleAddr, typeName, &mdType);
- if (mdType == 0)
- {
- return E_FAIL; // Failed to find type token
- }
-
- CLRDATA_ADDRESS cdaMethodTable = 0;
- if (FAILED(hr = g_sos->GetMethodDescFromToken(moduleAddr, mdType, &cdaMethodTable)) ||
- !IsValidToken(moduleAddr, mdType) ||
- cdaMethodTable == 0)
- {
- return FAILED(hr) ? hr : E_FAIL; // Invalid type token or type is not loaded yet
- }
-
- DacpMethodTableData vMethodTable;
- if ((hr = vMethodTable.Request(g_sos, cdaMethodTable)) != S_OK)
- {
- return FAILED(hr) ? hr : E_FAIL; // Failed to get method table data
- }
- if (vMethodTable.bIsShared)
- {
- ExtOut(" %s: %s\n", "Method table is shared (not implemented)", typeName);
- return E_NOTIMPL;
- }
-
- DacpMethodTableFieldData vMethodTableFields;
- if (FAILED(hr = vMethodTableFields.Request(g_sos, cdaMethodTable)))
- {
- return hr; // Failed to get field data
- }
-
- DacpModuleData vModule;
- if ((hr = vModule.Request(g_sos, vMethodTable.Module)) != S_OK)
- {
- return FAILED(hr) ? hr : E_FAIL; // Failed to get module data
- }
-
- DacpDomainLocalModuleData vDomainLocalModule;
- if ((hr = g_sos->GetDomainLocalModuleDataFromModule(vMethodTable.Module, &vDomainLocalModule)) != S_OK)
- {
- return FAILED(hr) ? hr : E_FAIL; // Failed to get domain local module data
- }
-
- ToRelease<IMetaDataImport> pImport = MDImportForModule(&vModule);
- CLRDATA_ADDRESS cdaField = vMethodTableFields.FirstField;
- DacpFieldDescData vFieldDesc;
- bool found = false;
- for (DWORD staticFieldIndex = 0; staticFieldIndex < vMethodTableFields.wNumStaticFields; )
- {
- if ((hr = vFieldDesc.Request(g_sos, cdaField)) != S_OK || vFieldDesc.Type >= ELEMENT_TYPE_MAX)
- {
- return FAILED(hr) ? hr : E_FAIL; // Failed to get member field desc
- }
- cdaField = vFieldDesc.NextField;
-
- if (!vFieldDesc.bIsStatic)
- {
- continue;
- }
-
- ++staticFieldIndex;
-
- if (vFieldDesc.Type != fieldType)
- {
- continue;
- }
-
- if (FAILED(hr = NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false)))
- {
- return hr; // Failed to get member field name
- }
-
- if (_wcscmp(g_mdName, wszFieldName) != 0)
- {
- continue;
- }
-
- if (vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal)
- {
- ExtOut(" %s: %s.%S\n", "Static field is thread-local or context-local (not implemented)", typeName, wszFieldName);
- return E_NOTIMPL;
- }
-
- found = true;
- break;
- }
-
- if (!found)
- {
- return E_FAIL; // Static field not found
- }
-
- DWORD_PTR pValueAddr = 0;
- GetStaticFieldPTR(&pValueAddr, &vDomainLocalModule, &vMethodTable, &vFieldDesc);
- if (pValueAddr == 0)
- {
- return E_FAIL; // Failed to get static field address
- }
-
- UINT64 value = 0;
- if (FAILED(MOVEBLOCK(value, pValueAddr, gElementTypeInfo[fieldType])))
- {
- return E_FAIL; // Failed to read static field
- }
-
- *pValue = value;
- return S_OK;
-}
-
// Return value: -1 = error,
// 0 = field not found,
// > 0 = offset to field from objAddr
}
-// Return value: -1 = error
-// -2 = not found
-// >= 0 = offset to field from cdaValue
-int GetValueFieldOffset(CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, DacpFieldDescData* pDacpFieldDescData)
-{
-#define EXITPOINT(EXPR) do { if(!(EXPR)) { return -1; } } while (0)
-
- const int NOT_FOUND = -2;
- DacpMethodTableData dmtd;
- DacpMethodTableFieldData vMethodTableFields;
- DacpFieldDescData vFieldDesc;
- DacpModuleData module;
- static DWORD numInstanceFields = 0; // Static due to recursion visiting parents
- numInstanceFields = 0;
-
- EXITPOINT(vMethodTableFields.Request(g_sos, cdaMT) == S_OK);
-
- EXITPOINT(dmtd.Request(g_sos, cdaMT) == S_OK);
- EXITPOINT(module.Request(g_sos, dmtd.Module) == S_OK);
- if (dmtd.ParentMethodTable)
- {
- DWORD retVal = GetValueFieldOffset(dmtd.ParentMethodTable, wszFieldName, pDacpFieldDescData);
- if (retVal != (DWORD)NOT_FOUND)
- {
- // Return in case of error or success. Fall through for field-not-found.
- return retVal;
- }
- }
-
- CLRDATA_ADDRESS dwAddr = vMethodTableFields.FirstField;
- ToRelease<IMetaDataImport> pImport = MDImportForModule(&module);
-
- while (numInstanceFields < vMethodTableFields.wNumInstanceFields)
- {
- EXITPOINT(vFieldDesc.Request(g_sos, dwAddr) == S_OK);
-
- if (!vFieldDesc.bIsStatic)
- {
- NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false);
- if (_wcscmp(wszFieldName, g_mdName) == 0)
- {
- if (pDacpFieldDescData != NULL)
- {
- *pDacpFieldDescData = vFieldDesc;
- }
- return vFieldDesc.dwOffset;
- }
- numInstanceFields++;
- }
-
- dwAddr = vFieldDesc.NextField;
- }
-
- // Field name not found...
- return NOT_FOUND;
-
-#undef EXITPOINT
-}
-
// Returns an AppDomain address if AssemblyPtr is loaded into that domain only. Otherwise
// returns NULL
CLRDATA_ADDRESS IsInOneDomainOnly(CLRDATA_ADDRESS AssemblyPtr)
const char *ElementTypeName (unsigned type);
void DisplayFields (CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodTableFieldData *pMTFD,
DWORD_PTR dwStartAddr = 0, BOOL bFirst=TRUE, BOOL bValueClass=FALSE);
-HRESULT GetNonSharedStaticFieldValueFromName(UINT64* pValue, DWORD_PTR moduleAddr, const char *typeName, __in_z LPCWSTR wszFieldName, CorElementType fieldType);
int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE);
int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE, DacpFieldDescData* pDacpFieldDescData=NULL);
int GetValueFieldOffset(CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, DacpFieldDescData* pDacpFieldDescData=NULL);
g_services->AddCommand("sosstatus", new sosCommand("SOSStatus"), "Displays the global SOS status.");
g_services->AddCommand("sosflush", new sosCommand("SOSFlush"), "Resets the internal cached state.");
g_services->AddCommand("syncblk", new sosCommand("SyncBlk"), "Displays the SyncBlock holder info.");
- g_services->AddCommand("threadpool", new sosCommand("ThreadPool"), "Displays info about the runtime thread pool.");
+ g_services->AddManagedCommand("threadpool", "Displays info about the runtime thread pool.");
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.");