src/Microsoft.Diagnostics.Runtime/ClrModule.cs (173 lines of code) (raw):

// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.Diagnostics.Runtime.AbstractDac; using Microsoft.Diagnostics.Runtime.Implementation; using Microsoft.Diagnostics.Runtime.Interfaces; using Microsoft.Diagnostics.Runtime.Utilities; namespace Microsoft.Diagnostics.Runtime { /// <summary> /// Represents a managed module in the target process. /// </summary> public sealed class ClrModule : IClrModule { private readonly IAbstractModuleHelpers _helpers; private readonly IAbstractClrNativeHeaps? _nativeHeapHelpers; private IAbstractMetadataReader? _metadata; private int _debugMode = int.MaxValue; private PdbInfo? _pdb; private (ulong MethodTable, int Token)[]? _typeDefMap; private (ulong MethodTable, int Token)[]? _typeRefMap; private ClrHeap? _heap; private readonly ulong _moduleAddress; private Lazy<ClrModuleInfo> _moduleInfo; internal ClrModuleInfo ModuleInfo => _moduleInfo.Value; internal ClrHeap Heap => _heap ??= AppDomain.Runtime.Heap; internal IDataReader DataReader { get; } internal ClrModule(ClrAppDomain domain, ulong moduleAddress, IAbstractModuleHelpers moduleHelpers, IAbstractClrNativeHeaps? nativeHeapHelpers, IDataReader dataReader) { DataReader = dataReader; AppDomain = domain; _helpers = moduleHelpers; _nativeHeapHelpers = nativeHeapHelpers; _moduleAddress = moduleAddress; _moduleInfo = new Lazy<ClrModuleInfo>(() => _helpers.GetModuleInfo(_moduleAddress)); } /// <summary> /// Gets the address of the clr!Module object. /// </summary> public ulong Address => _moduleAddress; /// <summary> /// Gets the AppDomain parent of this module. /// </summary> public ClrAppDomain AppDomain { get; } IClrAppDomain IClrModule.AppDomain => AppDomain; /// <summary> /// Gets the name of the assembly that this module is defined in. /// </summary> public string? AssemblyName => ModuleInfo.AssemblyName ?? ModuleInfo.FileName; /// <summary> /// Gets an identifier to uniquely represent this assembly. This value is not used by any other /// function in ClrMD, but can be used to group modules by their assembly. (Do not use AssemblyName /// for this, as reflection and other special assemblies can share the same name, but actually be /// different.) /// </summary> public ulong AssemblyAddress => ModuleInfo.Assembly; /// <summary> /// Gets the name of the module. /// </summary> public string? Name => ModuleInfo.FileName ?? ModuleInfo.AssemblyName; /// <summary> /// Gets a value indicating whether this module was created through <c>System.Reflection.Emit</c> (and thus has no associated /// file). /// </summary> public bool IsDynamic => ModuleInfo.IsDynamic; /// <summary> /// Gets a value indicating whether this module is an actual PEFile on disk. /// </summary> public bool IsPEFile => ModuleInfo.IsPEFile; /// <summary> /// Gets the base of the image loaded into memory. This may be 0 if there is not a physical /// file backing it. /// </summary> public ulong ImageBase => ModuleInfo.ImageBase; /// <summary> /// Returns the in memory layout for PEImages. /// </summary> public ModuleLayout Layout => ModuleInfo.Layout; /// <summary> /// Gets the size of the image in memory. /// </summary> public ulong Size { get { ulong size = ModuleInfo.Size; if (size != 0) { return size; } return GetSize(); } } /// <summary> /// Gets the location of metadata for this module in the process's memory. This is useful if you /// need to manually create IMetaData* objects. /// </summary> public ulong MetadataAddress => ModuleInfo.MetadataAddress; /// <summary> /// Gets the length of the metadata for this module. /// </summary> public ulong MetadataLength => ModuleInfo.MetadataSize; /// <summary> /// Gets the <c>IMetaDataImport</c> interface for this module. Note that this API does not provide a /// wrapper for <c>IMetaDataImport</c>. You will need to wrap the API yourself if you need to use this. /// </summary> internal IAbstractMetadataReader? MetadataReader => _metadata ??= _helpers?.GetMetadataReader(Address); /// <summary> /// The ThunkHeap associated with this Module. This is only available when debugging a .Net 8 or /// later runtime. /// </summary> public ulong ThunkHeap => ModuleInfo.ThunkHeap; /// <summary> /// The LoaderAllocator associated with this Module. This is only available when debugging a .Net 8 or /// later runtime. Note that this LoaderAllocator is usually share with its parent domain, except in /// rare circumstances, like for collectable assemblies. /// </summary> public ulong LoaderAllocator => ModuleInfo.LoaderAllocator; /// <summary> /// Enumerates the native heaps associated with the ThunkHeap. /// </summary> /// <returns>An enumerable of heaps.</returns> public IEnumerable<ClrNativeHeapInfo> EnumerateThunkHeap() => _nativeHeapHelpers?.EnumerateThunkHeaps(ThunkHeap) ?? Enumerable.Empty<ClrNativeHeapInfo>(); /// <summary> /// Enumerates the native heaps associated with the LoaderAllocator. This may be the same set of /// heaps enumerated by ClrAppDomain.EnumerateLoaderAllocatorHeaps if LoaderAllocator is not 0 and /// equals ClrAppDomain.LoaderAllocator. /// </summary> /// <returns>An enumerable of heaps.</returns> public IEnumerable<ClrNativeHeapInfo> EnumerateLoaderAllocatorHeaps() => _nativeHeapHelpers?.EnumerateLoaderAllocatorNativeHeaps(LoaderAllocator) ?? Enumerable.Empty<ClrNativeHeapInfo>(); /// <summary> /// Gets the debugging attributes for this module. /// </summary> public DebuggableAttribute.DebuggingModes DebuggingMode { get { if (_debugMode == int.MaxValue) _debugMode = GetDebugAttribute(); DebugOnly.Assert(_debugMode != int.MaxValue); return (DebuggableAttribute.DebuggingModes)_debugMode; } } private unsafe int GetDebugAttribute() { IAbstractMetadataReader? metadata = MetadataReader; if (metadata != null) { byte[] buffer = ArrayPool<byte>.Shared.Rent(4); int len = metadata.GetCustomAttributeData(0x20000001, "System.Diagnostics.DebuggableAttribute", buffer); if (len >= 4) { ushort opt = buffer[2]; ushort dbg = buffer[3]; return (dbg << 8) | opt; } ArrayPool<byte>.Shared.Return(buffer); } return (int)DebuggableAttribute.DebuggingModes.None; } /// <summary> /// Enumerates the constructed methodtables in this module which correspond to typedef tokens defined by this module. /// </summary> /// <returns>An enumeration of (ulong methodTable, uint typeDef).</returns> public IEnumerable<(ulong MethodTable, int Token)> EnumerateTypeDefToMethodTableMap() => _typeDefMap ??= (_helpers?.EnumerateTypeDefMap(Address) ?? Enumerable.Empty<(ulong, int)>()).ToArray(); public IEnumerable<(ulong MethodTable, int Token)> EnumerateTypeRefToMethodTableMap() => _typeRefMap ??= (_helpers?.EnumerateTypeRefMap(Address) ?? Enumerable.Empty<(ulong, int)>()).ToArray(); /// <summary> /// Attempts to obtain a ClrType based on the name of the type. Note this is a "best effort" due to /// the way that the DAC handles types. This function will fail for Generics, and types which have /// never been constructed in the target process. Please be sure to null-check the return value of /// this function. /// </summary> /// <param name="name">The name of the type. (This would be the EXACT value returned by ClrType.Name.)</param> /// <returns>The requested ClrType, or <see langword="null"/> if the type doesn't exist or if the runtime hasn't constructed it.</returns> public ClrType? GetTypeByName(string name) => AppDomain.Runtime.Heap.GetTypeByName(this, name); IClrType? IClrModule.GetTypeByName(string name) => GetTypeByName(name); /// <summary> /// Returns a name for the assembly. /// </summary> /// <returns>A name for the assembly.</returns> public override string? ToString() { if (string.IsNullOrEmpty(Name)) { if (!string.IsNullOrEmpty(AssemblyName)) return AssemblyName; if (IsDynamic) return "dynamic"; } return Name; } /// <summary> /// Gets the PDB information for this module. /// </summary> public PdbInfo? Pdb { get { if (_pdb is null) { using PEImage pefile = GetPEImage(); if (pefile.IsValid) _pdb = pefile.DefaultPdb; } return _pdb; } } private ulong GetSize() { try { using PEImage peimage = GetPEImage(); if (peimage.IsValid) { unchecked { return (ulong)peimage.IndexFileSize; } } } catch { } return 0; } private PEImage GetPEImage() { // Not correct, but as close as we can get until we add more information to the dac. bool virt = Layout != ModuleLayout.Flat; long size = (long)ModuleInfo.Size; ReadVirtualStream stream = new(DataReader, (long)ImageBase, size > 0 ? size : int.MaxValue); return new(stream, leaveOpen: false, isVirtual: virt); } public override bool Equals(object? obj) => Equals(obj as ClrModule); public bool Equals(ClrModule? other) { if (ReferenceEquals(this, other)) return true; if (other is null) return false; return Address == other.Address; } public bool Equals(IClrModule? other) { if (ReferenceEquals(this, other)) return true; if (other is null) return false; return Address == other.Address; } public override int GetHashCode() => Address.GetHashCode(); public static bool operator ==(ClrModule? left, ClrModule? right) { if (right is null) return left is null; return right.Equals(left); } public static bool operator !=(ClrModule? left, ClrModule? right) => !(left == right); } }