DbgShell/ClrMemDiag/ClrRuntime.cs

1534 строки
48 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Address = System.UInt64;
using Microsoft.Diagnostics.Runtime.Desktop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
namespace Microsoft.Diagnostics.Runtime
{
/// <summary>
/// Represents a single runtime in a target process or crash dump. This serves as the primary
/// entry point for getting diagnostic information.
/// </summary>
public abstract class ClrRuntime
{
/// <summary>
/// The ClrInfo of the current runtime.
/// </summary>
public ClrInfo ClrInfo { get; protected set; }
/// <summary>
/// Returns the DataTarget associated with this runtime.
/// </summary>
public abstract DataTarget DataTarget { get; }
/// <summary>
/// Whether or not the process is running in server GC mode or not.
/// </summary>
public bool ServerGC { get; protected set; }
/// <summary>
/// Enumerates the OS thread ID of GC threads in the runtime.
/// </summary>
public abstract IEnumerable<int> EnumerateGCThreads();
/// <summary>
/// The number of logical GC heaps in the process. This is always 1 for a workstation
/// GC, and usually it's the number of logical processors in a server GC application.
/// </summary>
public int HeapCount { get; protected set; }
/// <summary>
/// Returns the pointer size of the target process.
/// </summary>
abstract public int PointerSize { get; }
/// <summary>
/// Enumerates the list of appdomains in the process. Note the System appdomain and Shared
/// AppDomain are omitted.
/// </summary>
abstract public IList<ClrAppDomain> AppDomains { get; }
/// <summary>
/// Give access to the System AppDomain
/// </summary>
abstract public ClrAppDomain SystemDomain { get; }
/// <summary>
/// Give access to the Shared AppDomain
/// </summary>
abstract public ClrAppDomain SharedDomain { get; }
/// <summary>
/// Enumerates all managed threads in the process. Only threads which have previously run managed
/// code will be enumerated.
/// </summary>
abstract public IList<ClrThread> Threads { get; }
/// <summary>
/// Enumerates all objects currently on the finalizer queue. (Not finalizable objects, but objects
/// which have been collected and will be imminently finalized.)
/// </summary>
abstract public IEnumerable<Address> EnumerateFinalizerQueueObjectAddresses();
/// <summary>
/// Returns a ClrMethod by its internal runtime handle (on desktop CLR this is a MethodDesc).
/// </summary>
/// <param name="methodHandle">The method handle (MethodDesc) to look up.</param>
/// <returns>The ClrMethod for the given method handle, or null if no method was found.</returns>
abstract public ClrMethod GetMethodByHandle(Address methodHandle);
/// <summary>
/// Returns the CCW data associated with the given address. This is used when looking at stowed
/// exceptions in CLR.
/// </summary>
/// <param name="addr">The address of the CCW obtained from stowed exception data.</param>
/// <returns>The CcwData describing the given CCW, or null.</returns>
public abstract CcwData GetCcwDataByAddress(ulong addr);
/// <summary>
/// Read data out of the target process.
/// </summary>
/// <param name="address">The address to start the read from.</param>
/// <param name="buffer">The buffer to write memory to.</param>
/// <param name="bytesRequested">How many bytes to read (must be less than/equal to buffer.Length)</param>
/// <param name="bytesRead">The number of bytes actually read out of the process. This will be less than
/// bytes requested if the request falls off the end of an allocation.</param>
/// <returns>False if the memory is not readable (free or no read permission), true if *some* memory was read.</returns>
abstract public bool ReadMemory(Address address, byte[] buffer, int bytesRequested, out int bytesRead);
/// <summary>
/// Reads a pointer value out of the target process. This function reads only the target's pointer size,
/// so if this is used on an x86 target, only 4 bytes is read and written to val.
/// </summary>
/// <param name="address">The address to read from.</param>
/// <param name="value">The value at that address.</param>
/// <returns>True if the read was successful, false otherwise.</returns>
abstract public bool ReadPointer(Address address, out Address value);
/// <summary>
/// Enumerates a list of GC handles currently in the process. Note that this list may be incomplete
/// depending on the state of the process when we attempt to walk the handle table.
/// </summary>
/// <returns>The list of GC handles in the process, NULL on catastrophic error.</returns>
public abstract IEnumerable<ClrHandle> EnumerateHandles();
/// <summary>
/// Gets the GC heap of the process.
/// </summary>
abstract public ClrHeap GetHeap();
/// <summary>
/// Returns data on the CLR thread pool for this runtime.
/// </summary>
virtual public ClrThreadPool GetThreadPool() { throw new NotImplementedException(); }
/// <summary>
/// Enumerates regions of memory which CLR has allocated with a description of what data
/// resides at that location. Note that this does not return every chunk of address space
/// that CLR allocates.
/// </summary>
/// <returns>An enumeration of memory regions in the process.</returns>
abstract public IEnumerable<ClrMemoryRegion> EnumerateMemoryRegions();
/// <summary>
/// Attempts to get a ClrMethod for the given instruction pointer. This will return NULL if the
/// given instruction pointer is not within any managed method.
/// </summary>
abstract public ClrMethod GetMethodByAddress(Address ip);
/// <summary>
/// A list of all modules loaded into the process.
/// </summary>
public abstract IList<ClrModule> Modules { get; }
/// <summary>
/// Flushes the dac cache. This function MUST be called any time you expect to call the same function
/// but expect different results. For example, after walking the heap, you need to call Flush before
/// attempting to walk the heap again. After calling this function, you must discard ALL ClrMD objects
/// you have cached other than DataTarget and ClrRuntime and re-request the objects and data you need.
/// (E.G. if you want to use the ClrHeap object after calling flush, you must call ClrRuntime.GetHeap
/// again after Flush to get a new instance.)
/// </summary>
abstract public void Flush();
/// <summary>
/// Delegate called when the RuntimeFlushed event is triggered.
/// </summary>
/// <param name="runtime">Which runtime was flushed.</param>
public delegate void RuntimeFlushedCallback(ClrRuntime runtime);
/// <summary>
/// Called whenever the runtime is being flushed. All references to ClrMD objects need to be released
/// and not used for the given runtime after this call.
/// </summary>
public event RuntimeFlushedCallback RuntimeFlushed;
/// <summary>
/// Call when flushing the runtime.
/// </summary>
protected void OnRuntimeFlushed()
{
var evt = RuntimeFlushed;
if (evt != null)
evt(this);
}
/// <summary>
/// Whether or not the runtime has component method tables for arrays. This is an extra field in
/// array objects on the heap, which was removed in v4.6 of desktop clr.
/// </summary>
internal bool HasArrayComponentMethodTables
{
get
{
if (ClrInfo.Flavor == ClrFlavor.Desktop)
{
VersionInfo version = ClrInfo.Version;
if (version.Major > 4)
return false;
if (version.Major == 4 && version.Minor >= 6)
return false;
}
return true;
}
}
internal static bool IsPrimitive(ClrElementType cet)
{
return cet >= ClrElementType.Boolean && cet <= ClrElementType.Double
|| cet == ClrElementType.NativeInt || cet == ClrElementType.NativeUInt
|| cet == ClrElementType.Pointer || cet == ClrElementType.FunctionPointer;
}
internal static bool IsValueClass(ClrElementType cet)
{
return cet == ClrElementType.Struct;
}
internal static bool IsObjectReference(ClrElementType cet)
{
return cet == ClrElementType.String || cet == ClrElementType.Class
|| cet == ClrElementType.Array || cet == ClrElementType.SZArray
|| cet == ClrElementType.Object;
}
internal static Type GetTypeForElementType(ClrElementType type)
{
switch (type)
{
case ClrElementType.Boolean:
return typeof(bool);
case ClrElementType.Char:
return typeof(char);
case ClrElementType.Double:
return typeof(double);
case ClrElementType.Float:
return typeof(float);
case ClrElementType.Pointer:
case ClrElementType.NativeInt:
case ClrElementType.FunctionPointer:
return typeof(IntPtr);
case ClrElementType.NativeUInt:
return typeof(UIntPtr);
case ClrElementType.Int16:
return typeof(short);
case ClrElementType.Int32:
return typeof(int);
case ClrElementType.Int64:
return typeof(long);
case ClrElementType.Int8:
return typeof(sbyte);
case ClrElementType.UInt16:
return typeof(ushort);
case ClrElementType.UInt32:
return typeof(uint);
case ClrElementType.UInt64:
return typeof(ulong);
case ClrElementType.UInt8:
return typeof(byte);
default:
return null;
}
}
}
/// <summary>
/// Provides information about CLR's threadpool.
/// </summary>
public abstract class ClrThreadPool
{
/// <summary>
/// The total number of threadpool worker threads in the process.
/// </summary>
abstract public int TotalThreads { get; }
/// <summary>
/// The number of running threadpool threads in the process.
/// </summary>
abstract public int RunningThreads { get; }
/// <summary>
/// The number of idle threadpool threads in the process.
/// </summary>
abstract public int IdleThreads { get; }
/// <summary>
/// The minimum number of threadpool threads allowable.
/// </summary>
abstract public int MinThreads { get; }
/// <summary>
/// The maximum number of threadpool threads allowable.
/// </summary>
abstract public int MaxThreads { get; }
/// <summary>
/// Returns the minimum number of completion ports (if any).
/// </summary>
abstract public int MinCompletionPorts { get; }
/// <summary>
/// Returns the maximum number of completion ports.
/// </summary>
abstract public int MaxCompletionPorts { get; }
/// <summary>
/// Returns the CPU utilization of the threadpool (as a percentage out of 100).
/// </summary>
abstract public int CpuUtilization { get; }
/// <summary>
/// The number of free completion port threads.
/// </summary>
abstract public int FreeCompletionPortCount { get; }
/// <summary>
/// The maximum number of free completion port threads.
/// </summary>
abstract public int MaxFreeCompletionPorts { get; }
/// <summary>
/// Enumerates the work items on the threadpool (native side).
/// </summary>
abstract public IEnumerable<NativeWorkItem> EnumerateNativeWorkItems();
/// <summary>
/// Enumerates work items on the thread pool (managed side).
/// </summary>
/// <returns></returns>
abstract public IEnumerable<ManagedWorkItem> EnumerateManagedWorkItems();
}
/// <summary>
/// A managed threadpool object.
/// </summary>
public abstract class ManagedWorkItem
{
/// <summary>
/// The object address of this entry.
/// </summary>
public abstract ulong Object { get; }
/// <summary>
/// The type of Object.
/// </summary>
public abstract ClrType Type { get; }
}
/// <summary>
/// The type of work item this is.
/// </summary>
public enum WorkItemKind
{
/// <summary>
/// Unknown.
/// </summary>
Unknown,
/// <summary>
/// Callback for an async timer.
/// </summary>
AsyncTimer,
/// <summary>
/// Async callback.
/// </summary>
AsyncCallback,
/// <summary>
/// From ThreadPool.QueueUserWorkItem.
/// </summary>
QueueUserWorkItem,
/// <summary>
/// Timer delete callback.
/// </summary>
TimerDelete
}
/// <summary>
/// Represents a work item on CLR's thread pool (native side).
/// </summary>
public abstract class NativeWorkItem
{
/// <summary>
/// The type of work item this is.
/// </summary>
public abstract WorkItemKind Kind { get; }
/// <summary>
/// Returns the callback's address.
/// </summary>
public abstract Address Callback { get; }
/// <summary>
/// Returns the pointer to the user's data.
/// </summary>
public abstract Address Data { get; }
}
/// <summary>
/// Types of Clr handles.
/// </summary>
public enum HandleType
{
/// <summary>
/// Weak, short lived handle.
/// </summary>
WeakShort = 0,
/// <summary>
/// Weak, long lived handle.
/// </summary>
WeakLong = 1,
/// <summary>
/// Strong handle.
/// </summary>
Strong = 2,
/// <summary>
/// Strong handle, prevents relocation of target object.
/// </summary>
Pinned = 3,
/// <summary>
/// RefCounted handle (strong when the reference count is greater than 0).
/// </summary>
RefCount = 5,
/// <summary>
/// A weak handle which may keep its "secondary" object alive if the "target" object is also alive.
/// </summary>
Dependent = 6,
/// <summary>
/// A strong, pinned handle (keeps the target object from being relocated), used for async IO operations.
/// </summary>
AsyncPinned = 7,
/// <summary>
/// Strong handle used internally for book keeping.
/// </summary>
SizedRef = 8
}
/// <summary>
/// Represents a Clr handle in the target process.
/// </summary>
public class ClrHandle
{
/// <summary>
/// The address of the handle itself. That is, *Address == Object.
/// </summary>
public Address Address { get; set; }
/// <summary>
/// The Object the handle roots.
/// </summary>
public Address Object { get; set; }
/// <summary>
/// The the type of the Object.
/// </summary>
public ClrType Type { get; set; }
/// <summary>
/// Whether the handle is strong (roots the object) or not.
/// </summary>
public virtual bool IsStrong
{
get
{
switch (HandleType)
{
case HandleType.RefCount:
return RefCount > 0;
case HandleType.WeakLong:
case HandleType.WeakShort:
case HandleType.Dependent:
return false;
default:
return true;
}
}
}
/// <summary>
/// Whether or not the handle pins the object (doesn't allow the GC to
/// relocate it) or not.
/// </summary>
public virtual bool IsPinned
{
get
{
return HandleType == Runtime.HandleType.AsyncPinned || HandleType == Runtime.HandleType.Pinned;
}
}
/// <summary>
/// Gets the type of handle.
/// </summary>
public HandleType HandleType { get; set; }
/// <summary>
/// If this handle is a RefCount handle, this returns the reference count.
/// RefCount handles with a RefCount > 0 are strong.
/// NOTE: v2 CLR CANNOT determine the RefCount. We always set the RefCount
/// to 1 in a v2 query since a strong RefCount handle is the common case.
/// </summary>
public uint RefCount { get; set; }
/// <summary>
/// Set only if the handle type is a DependentHandle. Dependent handles add
/// an extra edge to the object graph. Meaning, this.Object now roots the
/// dependent target, but only if this.Object is alive itself.
/// NOTE: CLRs prior to v4.5 cannot obtain the dependent target. This field will
/// be 0 for any CLR prior to v4.5.
/// </summary>
public Address DependentTarget { get; set; }
/// <summary>
/// The type of the dependent target, if non 0.
/// </summary>
public ClrType DependentType { get; set; }
/// <summary>
/// The AppDomain the handle resides in.
/// </summary>
public ClrAppDomain AppDomain { get; set; }
/// <summary>
/// ToString override.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return HandleType.ToString() + " " + (Type != null ? Type.Name : "");
}
#region Internal
internal ClrHandle()
{
}
internal ClrHandle(Microsoft.Diagnostics.Runtime.Desktop.V45Runtime clr, ClrHeap heap, Microsoft.Diagnostics.Runtime.Desktop.HandleData handleData)
{
Address obj;
Address = handleData.Handle;
clr.ReadPointer(Address, out obj);
Object = obj;
Type = heap.GetObjectType(obj);
uint refCount = 0;
if (handleData.Type == (int)HandleType.RefCount)
{
if (handleData.IsPegged != 0)
refCount = handleData.JupiterRefCount;
if (refCount < handleData.RefCount)
refCount = handleData.RefCount;
if (Type != null)
{
if (Type.IsCCW(obj))
{
CcwData data = Type.GetCCWData(obj);
if (data != null && refCount < data.RefCount)
refCount = (uint)data.RefCount;
}
else if (Type.IsRCW(obj))
{
RcwData data = Type.GetRCWData(obj);
if (data != null && refCount < data.RefCount)
refCount = (uint)data.RefCount;
}
}
RefCount = refCount;
}
HandleType = (HandleType)handleData.Type;
AppDomain = clr.GetAppDomainByAddress(handleData.AppDomain);
if (HandleType == HandleType.Dependent)
{
DependentTarget = handleData.Secondary;
DependentType = heap.GetObjectType(handleData.Secondary);
}
}
#endregion
internal ClrHandle GetInteriorHandle()
{
if (this.HandleType != HandleType.AsyncPinned)
return null;
if (this.Type == null)
return null;
var field = this.Type.GetFieldByName("m_userObject");
if (field == null)
return null;
ulong obj;
object tmp = field.GetValue(this.Object);
if (!(tmp is ulong) || (obj = (ulong)tmp) == 0)
return null;
ClrType type = this.Type.Heap.GetObjectType(obj);
if (type == null)
return null;
ClrHandle result = new ClrHandle();
result.Object = obj;
result.Type = type;
result.Address = this.Address;
result.AppDomain = this.AppDomain;
result.HandleType = this.HandleType;
return result;
}
}
/// <summary>
/// Types of memory regions in a Clr process.
/// </summary>
public enum ClrMemoryRegionType
{
// Loader heaps
/// <summary>
/// Data on the loader heap.
/// </summary>
LowFrequencyLoaderHeap,
/// <summary>
/// Data on the loader heap.
/// </summary>
HighFrequencyLoaderHeap,
/// <summary>
/// Data on the stub heap.
/// </summary>
StubHeap,
// Virtual Call Stub heaps
/// <summary>
/// Clr implementation detail (this is here to allow you to distinguish from other
/// heap types).
/// </summary>
IndcellHeap,
/// <summary>
/// Clr implementation detail (this is here to allow you to distinguish from other
/// heap types).
/// </summary>
LookupHeap,
/// <summary>
/// Clr implementation detail (this is here to allow you to distinguish from other
/// heap types).
/// </summary>
ResolveHeap,
/// <summary>
/// Clr implementation detail (this is here to allow you to distinguish from other
/// heap types).
/// </summary>
DispatchHeap,
/// <summary>
/// Clr implementation detail (this is here to allow you to distinguish from other
/// heap types).
/// </summary>
CacheEntryHeap,
// Other regions
/// <summary>
/// Heap for JIT code data.
/// </summary>
JitHostCodeHeap,
/// <summary>
/// Heap for JIT loader data.
/// </summary>
JitLoaderCodeHeap,
/// <summary>
/// Heap for module jump thunks.
/// </summary>
ModuleThunkHeap,
/// <summary>
/// Heap for module lookup tables.
/// </summary>
ModuleLookupTableHeap,
/// <summary>
/// A segment on the GC heap (committed memory).
/// </summary>
GCSegment,
/// <summary>
/// A segment on the GC heap (reserved, but not committed, memory).
/// </summary>
ReservedGCSegment,
/// <summary>
/// A portion of Clr's handle table.
/// </summary>
HandleTableChunk
}
/// <summary>
/// Represents a region of memory in the process which Clr allocated and controls.
/// </summary>
public abstract class ClrMemoryRegion
{
/// <summary>
/// The start address of the memory region.
/// </summary>
public Address Address { get; set; }
/// <summary>
/// The size of the memory region in bytes.
/// </summary>
public ulong Size { get; set; }
/// <summary>
/// The type of heap/memory that the region contains.
/// </summary>
public ClrMemoryRegionType Type { get; set; }
/// <summary>
/// The AppDomain pointer that corresponds to this heap. You can obtain the
/// name of the AppDomain index or name by calling the appropriate function
/// on RuntimeBase.
/// Note: HasAppDomainData must be true before getting this property.
/// </summary>
abstract public ClrAppDomain AppDomain { get; }
/// <summary>
/// The Module pointer that corresponds to this heap. You can obtain the
/// filename of the module with this property.
/// Note: HasModuleData must be true or this property will be null.
/// </summary>
abstract public string Module { get; }
/// <summary>
/// Returns the heap number associated with this data. Returns -1 if no
/// GC heap is associated with this memory region.
/// </summary>
abstract public int HeapNumber { get; set; }
/// <summary>
/// Returns the gc segment type associated with this data. Only callable if
/// HasGCHeapData is true.
/// </summary>
abstract public GCSegmentType GCSegmentType { get; set; }
/// <summary>
/// Returns a string describing the region of memory (for example "JIT Code Heap"
/// or "GC Segment").
/// </summary>
/// <param name="detailed">Whether or not to include additional data such as the module,
/// AppDomain, or GC Heap associaed with it.</param>
abstract public string ToString(bool detailed);
/// <summary>
/// Equivalent to GetDisplayString(false).
/// </summary>
public override string ToString()
{
return ToString(false);
}
}
internal abstract class RuntimeBase : ClrRuntime
{
private static ulong[] s_emptyPointerArray = new ulong[0];
protected DacLibrary _library;
protected IXCLRDataProcess _dacInterface;
private MemoryReader _cache;
protected IDataReader _dataReader;
protected DataTargetImpl _dataTarget;
protected HeapBase _heap;
public HeapBase HeapBase
{
get
{
if (_heap == null)
GetHeap();
return _heap;
}
}
protected ICorDebug.ICorDebugProcess _corDebugProcess;
internal ICorDebug.ICorDebugProcess CorDebugProcess
{
get
{
if (_corDebugProcess == null)
_corDebugProcess = ICorDebug.CLRDebugging.CreateICorDebugProcess(ClrInfo.ModuleInfo.ImageBase, _library.DacDataTarget, _dataTarget.FileLoader);
return _corDebugProcess;
}
}
public RuntimeBase(ClrInfo info, DataTargetImpl dataTarget, DacLibrary lib)
{
Debug.Assert(lib != null);
Debug.Assert(lib.DacInterface != null);
ClrInfo = info;
_dataTarget = dataTarget;
_library = lib;
_dacInterface = _library.DacInterface;
InitApi();
_dacInterface.Flush();
IGCInfo data = GetGCInfo();
if (data == null)
throw new ClrDiagnosticsException("This runtime is not initialized and contains no data.", ClrDiagnosticsException.HR.RuntimeUninitialized);
ServerGC = data.ServerMode;
HeapCount = data.HeapCount;
CanWalkHeap = data.GCStructuresValid && !dataTarget.DataReader.IsMinidump;
_dataReader = dataTarget.DataReader;
}
public override DataTarget DataTarget
{
get { return _dataTarget; }
}
public void RegisterForRelease(object o)
{
if (o != null)
_library.AddToReleaseList(o);
}
public void RegisterForRelease(IModuleData module)
{
RegisterForRelease(module?.LegacyMetaDataImport);
}
public IDataReader DataReader
{
get { return _dataReader; }
}
protected abstract void InitApi();
public override int PointerSize
{
get { return IntPtr.Size; }
}
internal bool CanWalkHeap { get; private set; }
internal MemoryReader MemoryReader
{
get
{
if (_cache == null)
_cache = new MemoryReader(DataReader, 0x200);
return _cache;
}
set
{
_cache = value;
}
}
internal bool GetHeaps(out SubHeap[] heaps)
{
heaps = new SubHeap[HeapCount];
Dictionary<ulong, ulong> allocContexts = GetAllocContexts();
if (ServerGC)
{
ulong[] heapList = GetServerHeapList();
if (heapList == null)
return false;
bool succeeded = false;
for (int i = 0; i < heapList.Length; ++i)
{
IHeapDetails heap = GetSvrHeapDetails(heapList[i]);
if (heap == null)
continue;
heaps[i] = new SubHeap(heap, i);
heaps[i].AllocPointers = new Dictionary<ulong, ulong>(allocContexts);
if (heap.EphemeralAllocContextPtr != 0)
heaps[i].AllocPointers[heap.EphemeralAllocContextPtr] = heap.EphemeralAllocContextLimit;
succeeded = true;
}
return succeeded;
}
else
{
Debug.Assert(HeapCount == 1);
IHeapDetails heap = GetWksHeapDetails();
if (heap == null)
return false;
heaps[0] = new SubHeap(heap, 0);
heaps[0].AllocPointers = allocContexts;
heaps[0].AllocPointers[heap.EphemeralAllocContextPtr] = heap.EphemeralAllocContextLimit;
return true;
}
}
internal Dictionary<ulong, ulong> GetAllocContexts()
{
Dictionary<ulong, ulong> ret = new Dictionary<ulong, ulong>();
// Give a max number of threads to walk to ensure no infinite loops due to data
// inconsistency.
int max = 1024;
IThreadData thread = GetThread(GetFirstThread());
while (max-- > 0 && thread != null)
{
if (thread.AllocPtr != 0)
ret[thread.AllocPtr] = thread.AllocLimit;
if (thread.Next == 0)
break;
thread = GetThread(thread.Next);
}
return ret;
}
private struct StackRef
{
public ulong Address;
public ulong Object;
public StackRef(ulong stackPtr, ulong objRef)
{
Address = stackPtr;
Object = objRef;
}
}
public override IEnumerable<Address> EnumerateFinalizerQueueObjectAddresses()
{
SubHeap[] heaps;
if (GetHeaps(out heaps))
{
foreach (SubHeap heap in heaps)
{
foreach (Address objAddr in GetPointersInRange(heap.FQStart, heap.FQStop))
{
if (objAddr != 0)
yield return objAddr;
}
}
}
}
internal virtual IEnumerable<ClrRoot> EnumerateStackReferences(ClrThread thread, bool includeDead)
{
Address stackBase = thread.StackBase;
Address stackLimit = thread.StackLimit;
if (stackLimit <= stackBase)
{
Address tmp = stackLimit;
stackLimit = stackBase;
stackBase = tmp;
}
ClrAppDomain domain = GetAppDomainByAddress(thread.AppDomain);
ClrHeap heap = GetHeap();
var mask = ((ulong)(PointerSize - 1));
var cache = MemoryReader;
cache.EnsureRangeInCache(stackBase);
for (Address stackPtr = stackBase; stackPtr < stackLimit; stackPtr += (uint)PointerSize)
{
Address objRef;
if (cache.ReadPtr(stackPtr, out objRef))
{
// If the value isn't pointer aligned, it cannot be a managed pointer.
if (heap.IsInHeap(objRef))
{
ulong mt;
if (heap.ReadPointer(objRef, out mt))
{
ClrType type = null;
if (mt > 1024)
type = heap.GetObjectType(objRef);
if (type != null && !type.IsFree)
yield return new LocalVarRoot(stackPtr, objRef, type, domain, thread, false, true, false);
}
}
}
}
}
#region Abstract
internal abstract ulong GetFirstThread();
internal abstract IThreadData GetThread(ulong addr);
internal abstract IHeapDetails GetSvrHeapDetails(ulong addr);
internal abstract IHeapDetails GetWksHeapDetails();
internal abstract ulong[] GetServerHeapList();
internal abstract IThreadStoreData GetThreadStoreData();
internal abstract ISegmentData GetSegmentData(ulong addr);
internal abstract IGCInfo GetGCInfo();
internal abstract IMethodTableData GetMethodTableData(ulong addr);
internal abstract uint GetTlsSlot();
internal abstract uint GetThreadTypeIndex();
internal abstract ClrAppDomain GetAppDomainByAddress(ulong addr);
#endregion
#region Helpers
#region Request Helpers
protected bool Request(uint id, ulong param, byte[] output)
{
byte[] input = BitConverter.GetBytes(param);
return Request(id, input, output);
}
protected bool Request(uint id, uint param, byte[] output)
{
byte[] input = BitConverter.GetBytes(param);
return Request(id, input, output);
}
protected bool Request(uint id, byte[] input, byte[] output)
{
uint inSize = 0;
if (input != null)
inSize = (uint)input.Length;
uint outSize = 0;
if (output != null)
outSize = (uint)output.Length;
int result = _dacInterface.Request(id, inSize, input, outSize, output);
return result >= 0;
}
protected I Request<I, T>(uint id, byte[] input)
where T : struct, I
where I : class
{
byte[] output = GetByteArrayForStruct<T>();
if (!Request(id, input, output))
return null;
return ConvertStruct<I, T>(output);
}
protected I Request<I, T>(uint id, ulong param)
where T : struct, I
where I : class
{
byte[] output = GetByteArrayForStruct<T>();
if (!Request(id, param, output))
return null;
return ConvertStruct<I, T>(output);
}
protected I Request<I, T>(uint id, uint param)
where T : struct, I
where I : class
{
byte[] output = GetByteArrayForStruct<T>();
if (!Request(id, param, output))
return null;
return ConvertStruct<I, T>(output);
}
protected I Request<I, T>(uint id)
where T : struct, I
where I : class
{
byte[] output = GetByteArrayForStruct<T>();
if (!Request(id, null, output))
return null;
return ConvertStruct<I, T>(output);
}
protected bool RequestStruct<T>(uint id, ref T t)
where T : struct
{
byte[] output = GetByteArrayForStruct<T>();
if (!Request(id, null, output))
return false;
GCHandle handle = GCHandle.Alloc(output, GCHandleType.Pinned);
t = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return true;
}
protected bool RequestStruct<T>(uint id, ulong addr, ref T t)
where T : struct
{
byte[] input = new byte[sizeof(ulong)];
byte[] output = GetByteArrayForStruct<T>();
WriteValueToBuffer(addr, input, 0);
if (!Request(id, input, output))
return false;
GCHandle handle = GCHandle.Alloc(output, GCHandleType.Pinned);
t = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return true;
}
protected ulong[] RequestAddrList(uint id, int length)
{
byte[] bytes = new byte[length * sizeof(ulong)];
if (!Request(id, null, bytes))
return null;
ulong[] result = new ulong[length];
for (uint i = 0; i < length; ++i)
result[i] = BitConverter.ToUInt64(bytes, (int)(i * sizeof(ulong)));
return result;
}
protected ulong[] RequestAddrList(uint id, ulong param, int length)
{
byte[] bytes = new byte[length * sizeof(ulong)];
if (!Request(id, param, bytes))
return null;
ulong[] result = new ulong[length];
for (uint i = 0; i < length; ++i)
result[i] = BitConverter.ToUInt64(bytes, (int)(i * sizeof(ulong)));
return result;
}
#endregion
#region Marshalling Helpers
protected static string BytesToString(byte[] output)
{
int len = 0;
while (len < output.Length && (output[len] != 0 || output[len + 1] != 0))
len += 2;
if (len > output.Length)
len = output.Length;
return Encoding.Unicode.GetString(output, 0, len);
}
protected byte[] GetByteArrayForStruct<T>() where T : struct
{
return new byte[Marshal.SizeOf(typeof(T))];
}
protected I ConvertStruct<I, T>(byte[] bytes)
where I : class
where T : I
{
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
I result = (I)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return result;
}
protected int WriteValueToBuffer(IntPtr ptr, byte[] buffer, int offset)
{
ulong value = (ulong)ptr.ToInt64();
for (int i = offset; i < offset + IntPtr.Size; ++i)
{
buffer[i] = (byte)value;
value >>= 8;
}
return offset + IntPtr.Size;
}
protected int WriteValueToBuffer(int value, byte[] buffer, int offset)
{
for (int i = offset; i < offset + sizeof(int); ++i)
{
buffer[i] = (byte)value;
value >>= 8;
}
return offset + sizeof(int);
}
protected int WriteValueToBuffer(uint value, byte[] buffer, int offset)
{
for (int i = offset; i < offset + sizeof(int); ++i)
{
buffer[i] = (byte)value;
value >>= 8;
}
return offset + sizeof(int);
}
protected int WriteValueToBuffer(ulong value, byte[] buffer, int offset)
{
for (int i = offset; i < offset + sizeof(ulong); ++i)
{
buffer[i] = (byte)value;
value >>= 8;
}
return offset + sizeof(ulong);
}
#endregion
#region Data Read
public override bool ReadMemory(Address address, byte[] buffer, int bytesRequested, out int bytesRead)
{
return _dataReader.ReadMemory(address, buffer, bytesRequested, out bytesRead);
}
private byte[] _dataBuffer = new byte[8];
public bool ReadByte(Address addr, out byte value)
{
// todo: There's probably a more efficient way to implement this if ReadVirtual accepted an "out byte"
// "out dword", "out long", etc.
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, 1, out read))
return false;
Debug.Assert(read == 1);
value = _dataBuffer[0];
return true;
}
public bool ReadByte(Address addr, out sbyte value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, 1, out read))
return false;
Debug.Assert(read == 1);
value = (sbyte)_dataBuffer[0];
return true;
}
public bool ReadBoolean(ulong addr, out bool value)
{
int read = 0;
if (!ReadMemory(addr, _dataBuffer, 1, out read))
{
value = false;
return false;
}
Debug.Assert(read == 1);
value = _dataBuffer[0] != 0;
return true;
}
public bool ReadChar(ulong addr, out char value)
{
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(char), out read))
{
value = '\0';
return false;
}
Debug.Assert(read == sizeof(char));
value = BitConverter.ToChar(_dataBuffer, 0);
return true;
}
public bool ReadDword(ulong addr, out int value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(int), out read))
return false;
Debug.Assert(read == 4);
value = BitConverter.ToInt32(_dataBuffer, 0);
return true;
}
public bool ReadDword(ulong addr, out uint value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(uint), out read))
return false;
Debug.Assert(read == 4);
value = BitConverter.ToUInt32(_dataBuffer, 0);
return true;
}
public bool ReadFloat(ulong addr, out float value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(float), out read))
return false;
Debug.Assert(read == sizeof(float));
value = BitConverter.ToSingle(_dataBuffer, 0);
return true;
}
public bool ReadFloat(ulong addr, out double value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(double), out read))
return false;
Debug.Assert(read == sizeof(double));
value = BitConverter.ToDouble(_dataBuffer, 0);
return true;
}
public bool ReadString(ulong addr, out string value)
{
value = HeapBase.GetStringContents(addr);
return value != null;
}
public bool ReadShort(ulong addr, out short value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(short), out read))
return false;
Debug.Assert(read == sizeof(short));
value = BitConverter.ToInt16(_dataBuffer, 0);
return true;
}
public bool ReadShort(ulong addr, out ushort value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(ushort), out read))
return false;
Debug.Assert(read == sizeof(ushort));
value = BitConverter.ToUInt16(_dataBuffer, 0);
return true;
}
public bool ReadQword(ulong addr, out ulong value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(ulong), out read))
return false;
Debug.Assert(read == sizeof(ulong));
value = BitConverter.ToUInt64(_dataBuffer, 0);
return true;
}
public bool ReadQword(ulong addr, out long value)
{
value = 0;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, sizeof(long), out read))
return false;
Debug.Assert(read == sizeof(long));
value = BitConverter.ToInt64(_dataBuffer, 0);
return true;
}
public override bool ReadPointer(ulong addr, out ulong value)
{
int ptrSize = (int)PointerSize;
int read = 0;
if (!ReadMemory(addr, _dataBuffer, ptrSize, out read))
{
value = 0xcccccccc;
return false;
}
Debug.Assert(read == ptrSize);
if (ptrSize == 4)
value = (ulong)BitConverter.ToUInt32(_dataBuffer, 0);
else
value = (ulong)BitConverter.ToUInt64(_dataBuffer, 0);
return true;
}
public bool ReadPointer(ulong addr, out IntPtr value)
{
int read = 0;
if (!ReadMemory(addr, _dataBuffer, IntPtr.Size, out read))
{
value = IntPtr.Zero;
return false;
}
Debug.Assert(read == IntPtr.Size);
if (IntPtr.Size == 4)
value = new IntPtr(BitConverter.ToInt32(_dataBuffer, 0));
else
value = new IntPtr(BitConverter.ToInt64(_dataBuffer, 0));
return true;
}
public bool ReadPointer(ulong addr, out UIntPtr value)
{
int read = 0;
if (!ReadMemory(addr, _dataBuffer, IntPtr.Size, out read))
{
value = UIntPtr.Zero;
return false;
}
Debug.Assert(read == UIntPtr.Size);
if (UIntPtr.Size == 4)
value = new UIntPtr(BitConverter.ToUInt32(_dataBuffer, 0));
else
value = new UIntPtr(BitConverter.ToUInt64(_dataBuffer, 0));
return true;
}
internal IEnumerable<ulong> GetPointersInRange(ulong start, ulong stop)
{
// Possible we have empty list, or inconsistent data.
if (start >= stop)
return s_emptyPointerArray;
// Enumerate individually if we have too many.
ulong count = (stop - start) / (ulong)IntPtr.Size;
if (count > 4096)
return EnumeratePointersInRange(start, stop);
ulong[] array = new ulong[count];
byte[] tmp = new byte[(int)count * IntPtr.Size];
int read;
if (!ReadMemory(start, tmp, tmp.Length, out read))
return s_emptyPointerArray;
if (IntPtr.Size == 4)
for (uint i = 0; i < array.Length; ++i)
array[i] = BitConverter.ToUInt32(tmp, (int)(i * IntPtr.Size));
else
for (uint i = 0; i < array.Length; ++i)
array[i] = BitConverter.ToUInt64(tmp, (int)(i * IntPtr.Size));
return array;
}
private IEnumerable<Address> EnumeratePointersInRange(ulong start, ulong stop)
{
ulong obj;
for (ulong ptr = start; ptr < stop; ptr += (uint)IntPtr.Size)
{
if (!ReadPointer(ptr, out obj))
break;
yield return obj;
}
}
#endregion
#endregion
}
}