diff --git a/ClrMD.Extensions/ClrDynamic.cs b/ClrMD.Extensions/ClrDynamic.cs index 9cc4f4d..28ca07c 100644 --- a/ClrMD.Extensions/ClrDynamic.cs +++ b/ClrMD.Extensions/ClrDynamic.cs @@ -1,12 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Dynamic; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; +using ClrMD.Extensions.Core; using ClrMD.Extensions.LINQPad; using ClrMD.Extensions.Obfuscation; using LINQPad; @@ -267,6 +267,42 @@ public IEnumerable EnumerateDictionaryValues() } } + public T[] ToArray() + { + return (T[]) ToArray(typeof(T)); + } + + public Array ToArray(Type elemType) + { + int itemCount; + IEnumerable items; + + if (Type.IsArray) + { + itemCount = ArrayLength; + items = this; + } + else + { + var visual = Visualizer; + if (!(visual is ISingleCellEnumerableVisual simpleVisual)) + throw new InvalidOperationException("This is only valid on simple enumerable types"); + + itemCount = simpleVisual.Count; + items = simpleVisual.Items; + } + + + var array = Array.CreateInstance(elemType, itemCount); + int i = 0; + foreach (var val in items) + { + array.SetValue(val.ConvertContent(elemType), i++); + } + + return array; + } + private ClrDynamic GetInnerObject(ulong pointer, ClrType type) { ulong fieldAddress; @@ -525,12 +561,30 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) public override bool TryConvert(ConvertBinder binder, out object result) { - result = null; + result = ConvertContent(binder.ReturnType); + return true; + } + + public object ConvertContent(Type targetType) + { if (!HasSimpleValue) - return base.TryConvert(binder, out result); + { + if (targetType.IsArray) + return ToArray(targetType.GetElementType()); - result = Convert.ChangeType(SimpleValue, binder.ReturnType); - return true; + throw new InvalidOperationException($"{targetType.FullName} cannot be converted from {Type.Name}"); + } + + return ConvertValue(SimpleValue, targetType); + } + + private static object ConvertValue(object simpleValue, Type targetType) + { + if (!targetType.IsEnum) + return Convert.ChangeType(simpleValue, targetType); + + simpleValue = Convert.ChangeType(simpleValue, Enum.GetUnderlyingType(targetType)); + return Enum.ToObject(targetType, simpleValue); } #endregion @@ -635,187 +689,6 @@ private void ToDetailedString(StringBuilder builder, string indentation, bool in #endregion - #region SimpleValueHelper - - private static class SimpleValueHelper - { - private const string GuidTypeName = "System.Guid"; - private const string TimeSpanTypeName = "System.TimeSpan"; - private const string DateTimeTypeName = "System.DateTime"; - private const string IPAddressTypeName = "System.Net.IPAddress"; - - public static bool IsSimpleValue(ClrType type) - { - if (type.IsPrimitive || - type.IsString) - return true; - - switch (type.Name) - { - case GuidTypeName: - case TimeSpanTypeName: - case DateTimeTypeName: - case IPAddressTypeName: - return true; - } - - return false; - } - - public static object GetSimpleValue(ClrDynamic obj) - { - if (obj.IsNull()) - return null; - - ClrType type = obj.Type; - ClrHeap heap = type.Heap; - - if (type.IsPrimitive || type.IsString) - return type.GetValue(obj.Address); - - ulong address = obj.IsInterior ? obj.Address : obj.Address + (ulong)heap.PointerSize; - - switch (type.Name) - { - case GuidTypeName: - { - byte[] buffer = ReadBuffer(heap, address, 16); - return new Guid(buffer); - } - - case TimeSpanTypeName: - { - byte[] buffer = ReadBuffer(heap, address, 8); - long ticks = BitConverter.ToInt64(buffer, 0); - return new TimeSpan(ticks); - } - - case DateTimeTypeName: - { - byte[] buffer = ReadBuffer(heap, address, 8); - ulong dateData = BitConverter.ToUInt64(buffer, 0); - return GetDateTime(dateData); - } - - case IPAddressTypeName: - { - return GetIPAddress(obj); - } - } - - throw new InvalidOperationException(string.Format("SimpleValue not available for type '{0}'", type.Name)); - } - - public static string GetSimpleValueString(ClrDynamic obj) - { - object value = obj.SimpleValue; - - if (value == null) - return "{null}"; - - ClrType type = obj.Type; - if (type != null && type.IsEnum) - return type.GetEnumName(value) ?? value.ToString(); - - DateTime? dateTime = value as DateTime?; - if (dateTime != null) - return GetDateTimeString(dateTime.Value); - - return value.ToString(); - } - - private static byte[] ReadBuffer(ClrHeap heap, ulong address, int length) - { - byte[] buffer = new byte[length]; - int byteRead = heap.ReadMemory(address, buffer, 0, buffer.Length); - - if (byteRead != length) - throw new InvalidOperationException(string.Format("Expected to read {0} bytes and actually read {1}", length, byteRead)); - - return buffer; - } - - private static DateTime GetDateTime(ulong dateData) - { - const ulong DateTimeTicksMask = 0x3FFFFFFFFFFFFFFF; - const ulong DateTimeKindMask = 0xC000000000000000; - const ulong KindUnspecified = 0x0000000000000000; - const ulong KindUtc = 0x4000000000000000; - - long ticks = (long)(dateData & DateTimeTicksMask); - ulong internalKind = dateData & DateTimeKindMask; - - switch (internalKind) - { - case KindUnspecified: - return new DateTime(ticks, DateTimeKind.Unspecified); - - case KindUtc: - return new DateTime(ticks, DateTimeKind.Utc); - - default: - return new DateTime(ticks, DateTimeKind.Local); - } - } - - private static IPAddress GetIPAddress(ClrDynamic ipAddress) - { - const int AddressFamilyInterNetworkV6 = 23; - const int IPv4AddressBytes = 4; - const int IPv6AddressBytes = 16; - const int NumberOfLabels = IPv6AddressBytes / 2; - - byte[] bytes; - int family = (int)ipAddress["m_Family"].SimpleValue; - - if (family == AddressFamilyInterNetworkV6) - { - bytes = new byte[IPv6AddressBytes]; - int j = 0; - - var numbers = ipAddress["m_Numbers"]; - - for (int i = 0; i < NumberOfLabels; i++) - { - ushort number = (ushort)numbers[i].SimpleValue; - bytes[j++] = (byte)((number >> 8) & 0xFF); - bytes[j++] = (byte)(number & 0xFF); - } - } - else - { - long address = (long)ipAddress["m_Address"].SimpleValue; - bytes = new byte[IPv4AddressBytes]; - bytes[0] = (byte)(address); - bytes[1] = (byte)(address >> 8); - bytes[2] = (byte)(address >> 16); - bytes[3] = (byte)(address >> 24); - } - - return new IPAddress(bytes); - } - - private static string GetDateTimeString(DateTime dateTime) - { - return dateTime.ToString(@"yyyy-MM-dd HH\:mm\:ss.FFFFFFF") + GetDateTimeKindString(dateTime.Kind); - } - - private static string GetDateTimeKindString(DateTimeKind kind) - { - switch (kind) - { - case DateTimeKind.Unspecified: - return " (Unspecified)"; - case DateTimeKind.Utc: - return " (Utc)"; - default: - return " (Local)"; - } - } - } - - #endregion - #region LINQPad public IEnumerable GetNames() diff --git a/ClrMD.Extensions/Core/SimpleValueHelper.cs b/ClrMD.Extensions/Core/SimpleValueHelper.cs new file mode 100644 index 0000000..2eba7dc --- /dev/null +++ b/ClrMD.Extensions/Core/SimpleValueHelper.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Diagnostics.Runtime; + +namespace ClrMD.Extensions.Core +{ + internal static class SimpleValueHelper + { + private class SimpleValueHandler + { + public Func GetSimpleValue { get; } + public Func GetSimpleValueString { get; } + + private SimpleValueHandler(Func simpleValueExtractor, Func stringFormatter = null) + { + GetSimpleValue = simpleValueExtractor; + GetSimpleValueString = stringFormatter ?? (o => o?.ToString()); + } + + public static SimpleValueHandler Create(Func valueExtractor, Func formatter = null) + { + if (formatter is null) + return new SimpleValueHandler((o, addr) => valueExtractor(o, addr)); + return new SimpleValueHandler((o, addr) => valueExtractor(o, addr), o => formatter((T) o)); + } + } + + private static readonly Dictionary s_simpleValueHandlers = new Dictionary + { + ["System.String"] = SimpleValueHandler.Create(ExtractString), + ["System.Guid"] = SimpleValueHandler.Create(ExtractGuid), + ["System.TimeSpan"] = SimpleValueHandler.Create(ExtractTimeSpan), + ["System.DateTime"] = SimpleValueHandler.Create(ExtractDateTime, GetDateTimeString), + ["System.Net.IPAddress"] = SimpleValueHandler.Create(ExtractIPAddress), + ["System.Net.IPEndPoint"] = SimpleValueHandler.Create(ExtractIPEndPoint), + ["System.Net.DnsEndPoint"] = SimpleValueHandler.Create(ExtractDnsEndPoint), + ["System.Security.Cryptography.X509Certificates.X509Certificate"] = SimpleValueHandler.Create(ExtractCertificate, GetX509CertificateString), + ["System.Security.Cryptography.X509Certificates.X509Certificate2"] = SimpleValueHandler.Create(ExtractCertificate, GetX509CertificateString) + }; + + private static string ExtractString(ClrDynamic obj, ulong address) + { + return (string) obj.Type.GetValue(obj.Address); + } + + private static DnsEndPoint ExtractDnsEndPoint(ClrDynamic obj, ulong address) + { + var dyn = obj.Dynamic; + return new DnsEndPoint(dyn.m_Host, dyn.m_Port, dyn.m_Family); + } + + private static IPEndPoint ExtractIPEndPoint(ClrDynamic obj, ulong address) + { + var dyn = obj.Dynamic; + return new IPEndPoint((IPAddress)dyn.m_Address, (int)dyn.m_Port); + } + + private static IPAddress ExtractIPAddress(ClrDynamic obj, ulong _) + { + const int IPv6AddressBytes = 16; + const int NumberOfLabels = IPv6AddressBytes / 2; + + var dyn = obj.Dynamic; + + AddressFamily family = dyn.m_Family; + switch (family) + { + case AddressFamily.InterNetworkV6: + var bytes = new byte[IPv6AddressBytes]; + int j = 0; + + var numbers = obj["m_Numbers"].ToArray(); + + for (int i = 0; i < NumberOfLabels; i++) + { + ushort number = numbers[i]; + bytes[j++] = (byte) ((number >> 8) & 0xFF); + bytes[j++] = (byte) (number & 0xFF); + } + + return new IPAddress(bytes); + case AddressFamily.InterNetwork: + return new IPAddress((long) dyn.m_Address); + default: + throw new ArgumentException("This object is neither IPv4 nor IPv6!"); + } + } + + private static DateTime ExtractDateTime(ClrDynamic obj, ulong address) + { + byte[] buffer = ReadBuffer(obj.Heap, address, 8); + ulong dateData = BitConverter.ToUInt64(buffer, 0); + return GetDateTime(dateData); + } + + private static TimeSpan ExtractTimeSpan(ClrDynamic obj, ulong address) + { + byte[] buffer = ReadBuffer(obj.Heap, address, 8); + long ticks = BitConverter.ToInt64(buffer, 0); + return new TimeSpan(ticks); + } + + private static Guid ExtractGuid(ClrDynamic obj, ulong address) + { + byte[] buffer = ReadBuffer(obj.Heap, address, 16); + return new Guid(buffer); + } + + private static X509Certificate2 ExtractCertificate(ClrDynamic obj, ulong address) + { + ulong handle = obj.Dynamic.m_safeCertContext.handle; + if (handle == 0) + return new X509Certificate2(); + var certCtxPointer = ReadIndirectAddress(obj.Heap, handle); + if (certCtxPointer == 0) + return new X509Certificate2(); + + byte[] certContext = ReadBuffer(obj.Heap, certCtxPointer, 32); + + /* + typedef struct _CERT_CONTEXT { + DWORD dwCertEncodingType; + BYTE *pbCertEncoded; <------- padded in x64! + DWORD cbCertEncoded; + PCERT_INFO pCertInfo; + HCERTSTORE hCertStore; + } CERT_CONTEXT, *PCERT_CONTEXT; + */ + + ulong bytesPointer; + int bytesCount; + if (IntPtr.Size == 4) + { + bytesPointer = BitConverter.ToUInt32(certContext, 4); + bytesCount = BitConverter.ToInt32(certContext, 8); + } + else + { + bytesPointer = BitConverter.ToUInt64(certContext, 8); + bytesCount = BitConverter.ToInt32(certContext, 16); + } + + byte[] certBytes = ReadBuffer(obj.Heap, bytesPointer, bytesCount); + return new X509Certificate2(certBytes); + } + + private static string GetX509CertificateString(X509Certificate2 cert) + { + return $"{cert.Subject} ({cert.Thumbprint})"; + } + + public static bool IsSimpleValue(ClrType type) + { + return type.IsPrimitive || s_simpleValueHandlers.ContainsKey(type.Name); + } + + public static object GetSimpleValue(ClrDynamic obj) + { + if (obj.IsNull()) + { + return null; + } + + ClrType type = obj.Type; + ClrHeap heap = type.Heap; + + if (type.IsPrimitive) + { + return type.GetValue(obj.Address); + } + + if (!s_simpleValueHandlers.TryGetValue(obj.TypeName, out var handler)) + return false; + + ulong address = obj.IsInterior ? obj.Address : obj.Address + (ulong) heap.PointerSize; + return handler.GetSimpleValue(obj, address); + } + + public static string GetSimpleValueString(ClrDynamic obj) + { + object value = obj.SimpleValue; + + if (value == null) + return "{null}"; + + ClrType type = obj.Type; + if (type != null && type.IsEnum) + return type.GetEnumName(value) ?? value.ToString(); + + if (s_simpleValueHandlers.TryGetValue(obj.TypeName, out var handler)) + return handler.GetSimpleValueString(value); + + return value.ToString(); + } + + private static byte[] ReadBuffer(ClrHeap heap, ulong address, int length) + { + byte[] buffer = new byte[length]; + int byteRead = heap.ReadMemory(address, buffer, 0, buffer.Length); + + if (byteRead != length) + throw new InvalidOperationException(string.Format("Expected to read {0} bytes and actually read {1}", length, byteRead)); + + return buffer; + } + + private static ulong ReadIndirectAddress(ClrHeap heap, ulong address) + { + byte[] addr = ReadBuffer(heap, address, IntPtr.Size); + if (IntPtr.Size == 4) + return BitConverter.ToUInt32(addr, 0); + return BitConverter.ToUInt64(addr, 0); + } + + private static DateTime GetDateTime(ulong dateData) + { + const ulong DateTimeTicksMask = 0x3FFFFFFFFFFFFFFF; + const ulong DateTimeKindMask = 0xC000000000000000; + const ulong KindUnspecified = 0x0000000000000000; + const ulong KindUtc = 0x4000000000000000; + + long ticks = (long) (dateData & DateTimeTicksMask); + ulong internalKind = dateData & DateTimeKindMask; + + switch (internalKind) + { + case KindUnspecified: + return new DateTime(ticks, DateTimeKind.Unspecified); + + case KindUtc: + return new DateTime(ticks, DateTimeKind.Utc); + + default: + return new DateTime(ticks, DateTimeKind.Local); + } + } + + private static string GetDateTimeString(DateTime dateTime) + { + return dateTime.ToString(@"yyyy-MM-dd HH\:mm\:ss.FFFFFFF") + GetDateTimeKindString(dateTime.Kind); + } + + private static string GetDateTimeKindString(DateTimeKind kind) + { + switch (kind) + { + case DateTimeKind.Unspecified: + return " (Unspecified)"; + case DateTimeKind.Utc: + return " (Utc)"; + default: + return " (Local)"; + } + } + } +} \ No newline at end of file diff --git a/ClrMD.Extensions/LINQPad/TypeVisualizer.cs b/ClrMD.Extensions/LINQPad/TypeVisualizer.cs index 71ff5a5..8fa822f 100644 --- a/ClrMD.Extensions/LINQPad/TypeVisualizer.cs +++ b/ClrMD.Extensions/LINQPad/TypeVisualizer.cs @@ -37,7 +37,6 @@ public static void RegisterRegexVisualizer(string typeName, TypeVisualizer visua m_regexVisualizers.Add(new KeyValuePair(new Regex(typeName, RegexOptions.Compiled), visualizer)); } - public static TypeVisualizer TryGetVisualizer(ClrDynamic o) { TypeVisualizer visualizer; @@ -53,7 +52,6 @@ public static TypeVisualizer TryGetVisualizer(ClrDynamic o) if (m_visualizers.TryGetValue(name, out visualizer)) return visualizer; - foreach (var keyValuePair in m_regexVisualizers) { if (keyValuePair.Key.IsMatch(name)) @@ -68,16 +66,21 @@ public static TypeVisualizer TryGetVisualizer(ClrDynamic o) public abstract object GetValue(ClrDynamic o); } + public interface ISingleCellEnumerableVisual + { + int Count { get; } + IEnumerable Items { get; } + } + public class QueueVisualizer : TypeVisualizer { - public class QueueVisual + public class QueueVisual : ISingleCellEnumerableVisual { public int Count { get; set; } public IEnumerable Items { get; set; } } - public override object GetValue(ClrDynamic o) { int size = (int)o.Dynamic._size; @@ -106,7 +109,7 @@ public override object GetValue(ClrDynamic o) public class ListVisualizer : TypeVisualizer { - public class ListVisual + public class ListVisual : ISingleCellEnumerableVisual { public int Count { get; set; }