2021-09-20 18:20:01 +02:00

321 lines
13 KiB
C#

/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Debugger.DotNet.Metadata;
using Mono.Debugger.Soft;
namespace dnSpy.Debugger.DotNet.Mono.Impl.Evaluation {
readonly struct DbgDotNetRawValueFactory {
readonly DbgEngineImpl engine;
public DbgDotNetRawValueFactory(DbgEngineImpl engine) => this.engine = engine;
static DbgDotNetRawValueFactory() {
var ctor = typeof(DateTime).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(ulong) }, null);
Debug2.Assert(ctor is not null);
if (ctor is not null) {
var dm = new DynamicMethod("DateTime_ctor_UInt64", typeof(DateTime), new[] { typeof(ulong) }, true);
var ilg = dm.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0);
ilg.Emit(OpCodes.Newobj, ctor);
ilg.Emit(OpCodes.Ret);
DateTime_ctor_UInt64 = (Func<ulong, DateTime>)dm.CreateDelegate(typeof(Func<ulong, DateTime>));
}
}
static readonly Func<ulong, DateTime>? DateTime_ctor_UInt64;
public DbgDotNetRawValue Create(Value value, DmdType type) => Create(value, type, 0);
DbgDotNetRawValue Create(Value value, DmdType type, int recursionCounter) {
if (type.IsByRef)
type = type.GetElementType()!;
if (recursionCounter > 2 || value is null)
return GetRawValueDefault(value, type);
if (value is ObjectMirror && type.IsValueType)
value = ValueUtils.Unbox((ObjectMirror)value, type);
switch (value) {
case PrimitiveValue pv:
Debug2.Assert((pv.Type == ElementType.Object) == (pv.Value is null));
switch (pv.Type) {
case ElementType.Boolean: return new DbgDotNetRawValue(DbgSimpleValueType.Boolean, pv.Value);
case ElementType.Char: return new DbgDotNetRawValue(DbgSimpleValueType.CharUtf16, pv.Value);
case ElementType.I1: return new DbgDotNetRawValue(DbgSimpleValueType.Int8, pv.Value);
case ElementType.U1: return new DbgDotNetRawValue(DbgSimpleValueType.UInt8, pv.Value);
case ElementType.I2: return new DbgDotNetRawValue(DbgSimpleValueType.Int16, pv.Value);
case ElementType.U2: return new DbgDotNetRawValue(DbgSimpleValueType.UInt16, pv.Value);
case ElementType.I4: return new DbgDotNetRawValue(DbgSimpleValueType.Int32, pv.Value);
case ElementType.U4: return new DbgDotNetRawValue(DbgSimpleValueType.UInt32, pv.Value);
case ElementType.I8: return new DbgDotNetRawValue(DbgSimpleValueType.Int64, pv.Value);
case ElementType.U8: return new DbgDotNetRawValue(DbgSimpleValueType.UInt64, pv.Value);
case ElementType.R4: return new DbgDotNetRawValue(DbgSimpleValueType.Float32, pv.Value);
case ElementType.R8: return new DbgDotNetRawValue(DbgSimpleValueType.Float64, pv.Value);
case ElementType.I:
case ElementType.U:
if (type.AppDomain.Runtime.PointerSize == 4)
return new DbgDotNetRawValue(DbgSimpleValueType.Ptr32, (uint)(long)pv.Value!);
return new DbgDotNetRawValue(DbgSimpleValueType.Ptr64, (ulong)(long)pv.Value!);
case ElementType.Ptr:
ulong pval = (ulong)(long)pv.Value!;
if (pval == 0)
return new DbgDotNetRawValue(DbgSimpleValueType.Other, null);
if (type.AppDomain.Runtime.PointerSize == 4)
return new DbgDotNetRawValue(DbgSimpleValueType.Ptr32, (uint)pval);
return new DbgDotNetRawValue(DbgSimpleValueType.Ptr64, pval);
case ElementType.Object:
// This is a null value
return new DbgDotNetRawValue(DbgSimpleValueType.Other, null);
default:
throw new InvalidOperationException();
}
case EnumMirror em:
if (em.Fields.Length == 1)
return Create(em.Fields[0], type.GetEnumUnderlyingType(), recursionCounter + 1);
return GetRawValueDefault(value, type);
case StructMirror sm:
if (!type.IsEnum) {
// Boxed value
PrimitiveValue? bpv;
switch (DmdType.GetTypeCode(type)) {
case TypeCode.Boolean:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is bool)
return new DbgDotNetRawValue(DbgSimpleValueType.Boolean, bpv.Value);
break;
case TypeCode.Char:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is char)
return new DbgDotNetRawValue(DbgSimpleValueType.CharUtf16, bpv.Value);
break;
case TypeCode.SByte:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is sbyte)
return new DbgDotNetRawValue(DbgSimpleValueType.Int8, bpv.Value);
break;
case TypeCode.Byte:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is byte)
return new DbgDotNetRawValue(DbgSimpleValueType.UInt8, bpv.Value);
break;
case TypeCode.Int16:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is short)
return new DbgDotNetRawValue(DbgSimpleValueType.Int16, bpv.Value);
break;
case TypeCode.UInt16:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is ushort)
return new DbgDotNetRawValue(DbgSimpleValueType.UInt16, bpv.Value);
break;
case TypeCode.Int32:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is int)
return new DbgDotNetRawValue(DbgSimpleValueType.Int32, bpv.Value);
break;
case TypeCode.UInt32:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is uint)
return new DbgDotNetRawValue(DbgSimpleValueType.UInt32, bpv.Value);
break;
case TypeCode.Int64:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is long)
return new DbgDotNetRawValue(DbgSimpleValueType.Int64, bpv.Value);
break;
case TypeCode.UInt64:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is ulong)
return new DbgDotNetRawValue(DbgSimpleValueType.UInt64, bpv.Value);
break;
case TypeCode.Single:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is float)
return new DbgDotNetRawValue(DbgSimpleValueType.Float32, bpv.Value);
break;
case TypeCode.Double:
if (sm.Fields.Length == 1 && (bpv = sm.Fields[0] as PrimitiveValue) is not null && bpv.Value is double)
return new DbgDotNetRawValue(DbgSimpleValueType.Float64, bpv.Value);
break;
case TypeCode.Decimal:
return new DbgDotNetRawValue(DbgSimpleValueType.Decimal, ReadDecimal(sm));
case TypeCode.DateTime:
return new DbgDotNetRawValue(DbgSimpleValueType.DateTime, ReadDateTime(sm));
}
}
if (type == type.AppDomain.System_IntPtr || type == type.AppDomain.System_UIntPtr) {
if (sm.Fields.Length == 1 && sm.Fields[0] is PrimitiveValue pv && pv.Value is long) {
if (type.AppDomain.Runtime.PointerSize == 4)
return new DbgDotNetRawValue(DbgSimpleValueType.Ptr32, (uint)(long)pv.Value);
return new DbgDotNetRawValue(DbgSimpleValueType.Ptr64, (ulong)(long)pv.Value);
}
return GetRawValueDefault(value, type);
}
if (type.IsNullable) {
var info = GetNullableValues(sm);
if (info.valueType is null)
return GetRawValueDefault(value, type);
if (!info.hasValue)
return new DbgDotNetRawValue(DbgSimpleValueType.Other, null);
return Create(info.value, type.GetNullableElementType(), recursionCounter + 1);
}
return new DbgDotNetRawValue(DbgSimpleValueType.Other);
case ArrayMirror am:
return new DbgDotNetRawValue(DbgSimpleValueType.Other);
case StringMirror strVal:
return new DbgDotNetRawValue(DbgSimpleValueType.StringUtf16, strVal.Value);
case ObjectMirror om:
return new DbgDotNetRawValue(DbgSimpleValueType.Other);
default:
throw new InvalidOperationException();
}
}
static decimal ReadDecimal(StructMirror value) {
var fields = GetDecimalFields(value);
if (fields is not null) {
var decimalBits = new int[4];
decimalBits[0] = fields.Value.lo;
decimalBits[1] = fields.Value.mid;
decimalBits[2] = fields.Value.hi;
decimalBits[3] = fields.Value.flags;
try {
return new decimal(decimalBits);
}
catch (ArgumentException) {
}
}
return default;
}
static (int flags, int hi, int lo, int mid)? GetDecimalFields(StructMirror structMirror) {
var fields = structMirror.Type.GetFields().Where(a => !a.IsStatic && !a.IsLiteral).ToArray();
if (fields.Length != 4)
return default;
if (fields[0].Name != KnownMemberNames.Decimal_Flags_FieldName || fields[1].Name != KnownMemberNames.Decimal_Hi_FieldName || fields[2].Name != KnownMemberNames.Decimal_Lo_FieldName || fields[3].Name != KnownMemberNames.Decimal_Mid_FieldName)
return default;
var fieldValues = structMirror.Fields;
if (fieldValues.Length != 4)
return default;
if (!(fieldValues[0] is PrimitiveValue pvFlags && fieldValues[1] is PrimitiveValue pvHi &&
fieldValues[2] is PrimitiveValue pvLo && fieldValues[3] is PrimitiveValue pvMid))
return default;
// Mono using .NET source code
if (pvFlags.Value is int && pvHi.Value is int && pvLo.Value is int && pvMid.Value is int)
return ((int)pvFlags.Value, (int)pvHi.Value, (int)pvLo.Value, (int)pvMid.Value);
// Unity and older Mono
if (pvFlags.Value is uint && pvHi.Value is uint && pvLo.Value is uint && pvMid.Value is uint)
return ((int)(uint)pvFlags.Value, (int)(uint)pvHi.Value, (int)(uint)pvLo.Value, (int)(uint)pvMid.Value);
return default;
}
static DateTime ReadDateTime(StructMirror structMirror) {
var fields = structMirror.Type.GetFields().Where(a => !a.IsStatic && !a.IsLiteral).ToArray();
var values = structMirror.Fields;
if (fields.Length != values.Length)
return default;
if (fields.Length == 1) {
// Newer Mono using .NET source code
if (fields[0].Name != KnownMemberNames.DateTime_DateData_FieldName1 && fields[0].Name != KnownMemberNames.DateTime_DateData_FieldName2)
return default;
if (values[0] is PrimitiveValue pv && pv.Value is ulong) {
if (DateTime_ctor_UInt64 is not null)
return DateTime_ctor_UInt64((ulong)pv.Value);
return default;
}
}
else if (fields.Length == 2) {
// Unity and older Mono
if (fields[0].Name != KnownMemberNames.DateTime_Ticks_FieldName_Mono || fields[1].Name != KnownMemberNames.DateTime_Kind_FieldName_Mono)
return default;
var ticksValue = values[0] as StructMirror;
var kindValue = values[1] as EnumMirror;
if (ticksValue is null || kindValue is null)
return default;
if (ticksValue.Fields.Length != 1 || !(ticksValue.Fields[0] is PrimitiveValue ticksPM) || !(ticksPM.Value is long))
return default;
if (kindValue.Fields.Length != 1 || !(kindValue.Fields[0] is PrimitiveValue kindPM) || !(kindPM.Value is int))
return default;
try {
return new DateTime((long)ticksPM.Value, (DateTimeKind)(int)kindPM.Value);
}
catch {
}
return default;
}
return default;
}
(bool hasValue, Value value, TypeMirror valueType) GetNullableValues(StructMirror structMirror) {
var values = structMirror.Fields;
var fields = structMirror.Type.GetFields().Where(a => !a.IsStatic && !a.IsLiteral).ToArray();
if (values.Length != fields.Length)
return default;
if (values.Length != 2)
return default;
Value hasValue, value;
TypeMirror valueType;
if ((fields[0].Name == KnownMemberNames.Nullable_HasValue_FieldName_Mono || fields[0].Name == KnownMemberNames.Nullable_HasValue_FieldName) && fields[1].Name == KnownMemberNames.Nullable_Value_FieldName) {
hasValue = values[0];
value = values[1];
valueType = fields[1].FieldType;
}
else if ((fields[1].Name == KnownMemberNames.Nullable_HasValue_FieldName_Mono || fields[1].Name == KnownMemberNames.Nullable_HasValue_FieldName) && fields[0].Name == KnownMemberNames.Nullable_Value_FieldName) {
hasValue = values[1];
value = values[0];
valueType = fields[0].FieldType;
}
else
return default;
if (hasValue is PrimitiveValue pv && pv.Value is bool)
return ((bool)pv.Value, value, valueType);
return default;
}
DbgDotNetRawValue GetRawValueDefault(Value? value, DmdType type) {
if (value is PrimitiveValue pv && pv.Value is null)
return new DbgDotNetRawValue(DbgSimpleValueType.Other, null);
return new DbgDotNetRawValue(DbgSimpleValueType.Other);
}
}
}