/*
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);
}
}
}