/* 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 . */ using System; using System.Collections.Generic; using System.Diagnostics; using dndbg.COM.CorDebug; using dndbg.Engine; using dnSpy.Contracts.Debugger.DotNet.Evaluation; using dnSpy.Contracts.Debugger.Engine.Evaluation; using dnSpy.Contracts.Debugger.Evaluation; using dnSpy.Debugger.DotNet.Metadata; namespace dnSpy.Debugger.DotNet.CorDebug.Impl.Evaluation { readonly struct EvalArgumentResult { public string? ErrorMessage { get; } public CorValue? CorValue { get; } public EvalArgumentResult(string errorMessage) { ErrorMessage = errorMessage ?? throw new ArgumentNullException(nameof(errorMessage)); CorValue = null; } public EvalArgumentResult(CorValue? corValue) { ErrorMessage = null; CorValue = corValue ?? throw new ArgumentNullException(nameof(corValue)); } internal static EvalArgumentResult Create(EvalResult? res, int hr) { if (res is null || res.Value.WasException) return new EvalArgumentResult(CordbgErrorHelper.GetErrorMessage(hr)); if (res.Value.WasCustomNotification) return new EvalArgumentResult(CordbgErrorHelper.FuncEvalRequiresAllThreadsToRun); if (res.Value.WasCancelled) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.FuncEvalTimedOut); return new EvalArgumentResult(res.Value.ResultOrException!); } } readonly struct EvalArgumentConverter { readonly DbgEngineImpl engine; readonly DnEval dnEval; readonly CorAppDomain appDomain; readonly DmdAppDomain reflectionAppDomain; readonly List createdValues; public EvalArgumentConverter(DbgEngineImpl engine, DnEval dnEval, CorAppDomain appDomain, DmdAppDomain reflectionAppDomain, List createdValues) { this.engine = engine; this.dnEval = dnEval; this.appDomain = appDomain; this.reflectionAppDomain = reflectionAppDomain; this.createdValues = createdValues; } public unsafe EvalArgumentResult Convert(object? value, DmdType defaultType, out DmdType type) { if (value is null) { type = defaultType; return new EvalArgumentResult(dnEval.CreateNull()); } if (value is DbgValue dbgValue) { value = dbgValue.InternalValue; if (value is null) { type = defaultType; return new EvalArgumentResult(dnEval.CreateNull()); } } if (value is DbgDotNetValueImpl dnValueImpl) { type = dnValueImpl.Type; var corValue = dnValueImpl.TryGetCorValue(); if (corValue is not null) return new EvalArgumentResult(corValue); return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); } DmdType? origType = null; if (value is DbgDotNetValue dnValue) { var rawValue = dnValue.GetRawValue(); if (rawValue.HasRawValue) { value = rawValue.RawValue; if (value is null) { type = defaultType; return new EvalArgumentResult(dnEval.CreateNull()); } } origType = dnValue.Type; } if (value is string s) { type = reflectionAppDomain.System_String; var res = dnEval.CreateString(s, out var hr); if (res?.ResultOrException is CorValue corValue) return new EvalArgumentResult(AddValue(reflectionAppDomain.System_String, corValue)); return EvalArgumentResult.Create(res, hr); } switch (Type.GetTypeCode(value.GetType())) { case TypeCode.Boolean: return CreateByte(type = origType ?? reflectionAppDomain.System_Boolean, (byte)((bool)value ? 1 : 0)); case TypeCode.Char: return CreateUInt16(type = origType ?? reflectionAppDomain.System_Char, (char)value); case TypeCode.SByte: return CreateByte(type = origType ?? reflectionAppDomain.System_SByte, (byte)(sbyte)value); case TypeCode.Byte: return CreateByte(type = origType ?? reflectionAppDomain.System_Byte, (byte)value); case TypeCode.Int16: return CreateUInt16(type = origType ?? reflectionAppDomain.System_Int16, (ushort)(short)value); case TypeCode.UInt16: return CreateUInt16(type = origType ?? reflectionAppDomain.System_UInt16, (ushort)value); case TypeCode.Int32: return CreateUInt32(type = origType ?? reflectionAppDomain.System_Int32, (uint)(int)value); case TypeCode.UInt32: return CreateUInt32(type = origType ?? reflectionAppDomain.System_UInt32, (uint)value); case TypeCode.Int64: return CreateUInt64(type = origType ?? reflectionAppDomain.System_Int64, (ulong)(long)value); case TypeCode.UInt64: return CreateUInt64(type = origType ?? reflectionAppDomain.System_UInt64, (ulong)value); case TypeCode.Single: type = origType ?? reflectionAppDomain.System_Single; return CreateSingle((float)value); case TypeCode.Double: type = origType ?? reflectionAppDomain.System_Double; return CreateDouble((double)value); case TypeCode.Decimal: type = reflectionAppDomain.System_Decimal; return CreateDecimal((decimal)value); default: if (value.GetType() == typeof(IntPtr)) { type = origType ?? reflectionAppDomain.System_IntPtr; if (IntPtr.Size == 4) return CreateUInt32(reflectionAppDomain.System_IntPtr, (uint)((IntPtr)value).ToInt32()); return CreateUInt64(reflectionAppDomain.System_IntPtr, (ulong)((IntPtr)value).ToInt64()); } if (value.GetType() == typeof(UIntPtr)) { type = origType ?? reflectionAppDomain.System_UIntPtr; if (IntPtr.Size == 4) return CreateUInt32(reflectionAppDomain.System_UIntPtr, ((UIntPtr)value).ToUInt32()); return CreateUInt64(reflectionAppDomain.System_UIntPtr, ((UIntPtr)value).ToUInt64()); } if (value is Array array && array.Rank == 1 && value.GetType().GetElementType()!.MakeArrayType() == value.GetType()) { switch (Type.GetTypeCode(value.GetType().GetElementType())) { case TypeCode.Boolean: var ba = (bool[])value; fixed (void* p = ba) return ConvertSZArray(p, ba.Length, 1, reflectionAppDomain.System_Boolean, out type); case TypeCode.Char: var bc = (char[])value; fixed (void* p = bc) return ConvertSZArray(p, bc.Length, 2, reflectionAppDomain.System_Char, out type); case TypeCode.SByte: var bsb = (sbyte[])value; fixed (void* p = bsb) return ConvertSZArray(p, bsb.Length, 1, reflectionAppDomain.System_SByte, out type); case TypeCode.Byte: var bb = (byte[])value; fixed (void* p = bb) return ConvertSZArray(p, bb.Length, 1, reflectionAppDomain.System_Byte, out type); case TypeCode.Int16: var bi16 = (short[])value; fixed (void* p = bi16) return ConvertSZArray(p, bi16.Length, 2, reflectionAppDomain.System_Int16, out type); case TypeCode.UInt16: var bu16 = (ushort[])value; fixed (void* p = bu16) return ConvertSZArray(p, bu16.Length, 2, reflectionAppDomain.System_UInt16, out type); case TypeCode.Int32: var bi32 = (int[])value; fixed (void* p = bi32) return ConvertSZArray(p, bi32.Length, 4, reflectionAppDomain.System_Int32, out type); case TypeCode.UInt32: var bu32 = (uint[])value; fixed (void* p = bu32) return ConvertSZArray(p, bu32.Length, 4, reflectionAppDomain.System_UInt32, out type); case TypeCode.Int64: var bi64 = (long[])value; fixed (void* p = bi64) return ConvertSZArray(p, bi64.Length, 8, reflectionAppDomain.System_Int64, out type); case TypeCode.UInt64: var bu64 = (ulong[])value; fixed (void* p = bu64) return ConvertSZArray(p, bu64.Length, 8, reflectionAppDomain.System_UInt64, out type); case TypeCode.Single: var br4 = (float[])value; fixed (void* p = br4) return ConvertSZArray(p, br4.Length, 4, reflectionAppDomain.System_Single, out type); case TypeCode.Double: var br8 = (double[])value; fixed (void* p = br8) return ConvertSZArray(p, br8.Length, 8, reflectionAppDomain.System_Double, out type); case TypeCode.String: return ConvertSZArray((string[])value, out type); default: break; } } break; } type = defaultType; return new EvalArgumentResult($"Func-eval: Can't convert type {value.GetType()} to a debugger value"); } EvalArgumentResult ConvertSZArray(string[] array, out DmdType type) { var elementType = reflectionAppDomain.System_String; type = elementType.MakeArrayType(); var corElementType = GetType(elementType); var res = dnEval.CreateSZArray(corElementType, array.Length, out int hr); if (res is null || !res.Value.NormalResult) return EvalArgumentResult.Create(res, hr); if (!IsInitialized(array)) return EvalArgumentResult.Create(res, hr); Debug.Assert(array.Length > 0); CorValue? elem = null; bool error = true; try { var arrayValue = res.Value.ResultOrException!; for (int i = 0; i < array.Length; i++) { var s = array[i]; if (s is null) continue; var stringValueRes = Convert(s, elementType, out var type2); if (stringValueRes.ErrorMessage is not null) return stringValueRes; if (!stringValueRes.CorValue!.IsReference) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); CorValue? av = arrayValue; if (av.IsReference) { av = av.GetDereferencedValue(out hr); if (av is null) return new EvalArgumentResult(CordbgErrorHelper.GetErrorMessage(hr)); } if (av?.IsArray != true) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); Debug2.Assert(elem is null); elem = av.GetElementAtPosition(i, out hr); if (elem is null) return new EvalArgumentResult(CordbgErrorHelper.GetErrorMessage(hr)); hr = elem.SetReferenceAddress(stringValueRes.CorValue.ReferenceAddress); if (hr != 0) return new EvalArgumentResult(CordbgErrorHelper.GetErrorMessage(hr)); engine.DisposeHandle_CorDebug(elem); elem = null; } var eaRes = new EvalArgumentResult(AddValue(type, res.Value.ResultOrException!)); error = false; return eaRes; } finally { if (error) engine.DisposeHandle_CorDebug(res.Value.ResultOrException); engine.DisposeHandle_CorDebug(elem); } } unsafe EvalArgumentResult ConvertSZArray(void* array, int length, int elementSize, DmdType elementType, out DmdType type) { type = elementType.MakeArrayType(); var corElementType = GetType(elementType); var res = dnEval.CreateSZArray(corElementType, length, out int hr); if (res is null || !res.Value.NormalResult) return EvalArgumentResult.Create(res, hr); if (!IsInitialized(array, length * elementSize)) return EvalArgumentResult.Create(res, hr); bool error = true; try { Debug.Assert(length > 0); CorValue? arrayValue = res.Value.ResultOrException!; if (arrayValue.IsReference) { arrayValue = arrayValue.GetDereferencedValue(out hr); if (arrayValue is not null) return new EvalArgumentResult(CordbgErrorHelper.GetErrorMessage(hr)); } Debug.Assert(arrayValue?.IsArray == true); if (arrayValue?.IsArray != true) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); var addr = DbgDotNetValueImpl.GetArrayAddress(arrayValue); if (addr is null) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); if (!(appDomain.Process is CorProcess process)) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); hr = process.WriteMemory(addr.Value.Address, array, length * elementSize, out int sizeWritten); if (hr < 0 || sizeWritten != length * elementSize) return new EvalArgumentResult(PredefinedEvaluationErrorMessages.InternalDebuggerError); var eaRes = new EvalArgumentResult(AddValue(type, res.Value.ResultOrException)); error = false; return eaRes; } finally { if (error) engine.DisposeHandle_CorDebug(res.Value.ResultOrException); } } static bool IsInitialized(T[] array) where T : class { for (int i = 0; i < array.Length; i++) { if (array[i] is not null) return true; } return false; } static unsafe bool IsInitialized(void* array, int length) { if (IntPtr.Size == 4) { var p = (uint*)array; while (length >= 4) { if (*p != 0) return true; length -= 4; p++; } var pb = (byte*)p; while (length > 0) { if (*pb != 0) return true; pb++; length--; } } else { var p = (ulong*)array; while (length >= 8) { if (*p != 0) return true; length -= 8; p++; } var pb = (byte*)p; while (length > 0) { if (*pb != 0) return true; pb++; length--; } } return false; } CorType GetType(DmdType type) => CorDebugTypeCreator.GetType(engine, appDomain, type); CorValue? AddValue(DmdType type, CorValue? value) { if (value is not null && !value.IsNull && !value.IsHandle && value.IsReference && !type.IsPointer && !type.IsFunctionPointer && !type.IsByRef) value = value.GetDereferencedValue(out int hr)?.CreateHandle(CorDebugHandleType.HANDLE_STRONG) ?? value; if (value is not null) { try { createdValues.Add(value); } catch { engine.DisposeHandle_CorDebug(value); throw; } } return value; } EvalArgumentResult CreateNoConstructor(DmdType type) { var res = dnEval.CreateDontCallConstructor(GetType(type), out int hr); var argRes = EvalArgumentResult.Create(res, hr); var value = AddValue(type, argRes.CorValue); if (value is not null) return new EvalArgumentResult(value); return argRes; } EvalArgumentResult CreateByte(DmdType type, byte value) { var res = CreateNoConstructor(type); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(new byte[1] { value }); return res; } EvalArgumentResult CreateUInt16(DmdType type, ushort value) { var res = CreateNoConstructor(type); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(BitConverter.GetBytes(value)); return res; } EvalArgumentResult CreateUInt32(DmdType type, uint value) { var res = CreateNoConstructor(type); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(BitConverter.GetBytes(value)); return res; } EvalArgumentResult CreateUInt64(DmdType type, ulong value) { var res = CreateNoConstructor(type); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(BitConverter.GetBytes(value)); return res; } EvalArgumentResult CreateSingle(float value) { var res = CreateNoConstructor(reflectionAppDomain.System_Single); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(BitConverter.GetBytes(value)); return res; } EvalArgumentResult CreateDouble(double value) { var res = CreateNoConstructor(reflectionAppDomain.System_Double); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(BitConverter.GetBytes(value)); return res; } EvalArgumentResult CreateDecimal(decimal value) { var res = CreateNoConstructor(reflectionAppDomain.System_Decimal); if (res.ErrorMessage is not null) return res; Debug2.Assert(res.CorValue!.DereferencedValue is not null && res.CorValue.DereferencedValue.BoxedValue is not null); if (value != 0) res.CorValue.DereferencedValue.BoxedValue.WriteGenericValue(GetBytes(value)); return res; } static byte[] GetBytes(decimal d) { var decimalBits = decimal.GetBits(d); var bytes = new byte[16]; WriteInt32(bytes, 0, decimalBits[3]); WriteInt32(bytes, 4, decimalBits[2]); WriteInt32(bytes, 8, decimalBits[0]); WriteInt32(bytes, 12, decimalBits[1]); return bytes; } static void WriteInt32(byte[] dest, int index, int v) { dest[index + 0] = (byte)v; dest[index + 1] = (byte)(v >> 8); dest[index + 2] = (byte)(v >> 16); dest[index + 3] = (byte)(v >> 24); } } }