/* 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.Diagnostics; using dnSpy.Debugger.DotNet.Metadata; namespace dnSpy.Debugger.DotNet.Interpreter { /// /// IL stack value kind /// public enum ILValueKind { /// /// 32-bit integer. 1-byte and 2-byte integers are sign/zero extended to 32 bits. Booleans and chars are zero extended. /// Int32, /// /// 64-bit integer /// Int64, /// /// 64-bit float (32-bit floats are extended to 64-bit floats) /// Float, /// /// Unmanaged pointer or native int /// NativeInt, /// /// Managed pointer /// ByRef, /// /// Any other reference type or value type /// Type, } /// /// A value that can be stored on the IL stack /// public abstract class ILValue { /// /// Gets the stack value kind /// public abstract ILValueKind Kind { get; } /// /// true if this is a null value /// public virtual bool IsNull => false; /// /// Makes a copy of this instance so the new instance can be pushed onto the stack. The default implementation /// returns itself. Only mutable value types need to override this method. /// /// public virtual ILValue Clone() => this; /// /// Gets the type of the value or null if it's unknown, eg. it's a null reference /// public abstract DmdType? Type { get; } /// /// Loads an instance field. Returns null if it's not supported. /// /// Field /// public virtual ILValue? LoadField(DmdFieldInfo field) => null; /// /// Stores a value in an instance field. Returns false if it's not supported. /// /// Field /// Value /// public virtual bool StoreField(DmdFieldInfo field, ILValue value) => false; /// /// Returns the address of an instance field. Returns null if it's not supported. /// /// Field /// public virtual ILValue? LoadFieldAddress(DmdFieldInfo field) => null; /// /// Calls an instance method. The method could be a CLR-generated method, eg. an array Address() method, see . /// Returns false if it's not supported. /// /// true if this is a virtual call, false if it's a non-virtual call /// Method /// Arguments. The hidden 'this' value isn't included, it's this instance. /// Updated with the return value. Can be null if the return type is /// public virtual bool Call(bool isCallvirt, DmdMethodBase method, ILValue[] arguments, out ILValue? returnValue) { returnValue = null; return false; } /// /// Calls an instance method or returns false on failure /// /// Method address /// Method signature /// Method arguments /// Return value. It's ignored if the method returns /// public virtual bool CallIndirect(DmdMethodSignature methodSig, ILValue methodAddress, ILValue[] arguments, out ILValue? returnValue) { returnValue = null; return false; } /// /// Boxes this instance. Returns null if it's not supported. /// /// Target type /// public virtual ILValue? Box(DmdType type) => null; /// /// Unboxes this instance. Returns null if it's not supported. /// /// Target type /// public virtual ILValue? UnboxAny(DmdType type) => null; /// /// Unboxes this instance. Returns null if it's not supported. /// /// Target type /// public virtual ILValue? Unbox(DmdType type) => null; /// /// Loads an SZ array element. Returns null if it's not supported. /// /// Type of value to load /// Array index /// Optional element type (eg. it's the ldelem instruction) /// public virtual ILValue? LoadSZArrayElement(LoadValueType loadValueType, long index, DmdType elementType) => null; /// /// Writes an SZ array element. Returns false if it's not supported. /// /// Type of value to store /// Index /// Value /// Optional element type (eg. it's the stelem instruction) /// public virtual bool StoreSZArrayElement(LoadValueType loadValueType, long index, ILValue value, DmdType elementType) => false; /// /// Loads the address of an SZ array element. Returns null if it's not supported. /// /// Index /// Element type /// public virtual ILValue? LoadSZArrayElementAddress(long index, DmdType elementType) => null; /// /// Gets the length of an SZ array. Returns false if it's not supported. /// /// Updated with the length of the array /// public virtual bool GetSZArrayLength(out long length) { length = 0; return false; } /// /// Loads a value. Returns null if it's not supported. /// /// Type /// Type of value to load /// public virtual ILValue? LoadIndirect(DmdType type, LoadValueType loadValueType) => null; /// /// Stores a value. Returns false if it's not supported. /// /// Type /// Type of value to store /// Value /// public virtual bool StoreIndirect(DmdType type, LoadValueType loadValueType, ILValue value) => false; /// /// Clears the memory. Returns false if it's not supported. /// /// Type /// public virtual bool InitializeObject(DmdType type) => false; /// /// Copies to this value. Returns false if it's not supported. /// /// Type /// Source value /// public virtual bool CopyObject(DmdType type, ILValue source) => false; /// /// Initializes memory. Returns false if it's not supported. /// /// Value to write /// Size of data /// public virtual bool InitializeMemory(byte value, long size) => false; /// /// Copies memory to this value. Returns false if it's not supported. /// /// Source value /// Size in bytes /// public virtual bool CopyMemory(ILValue source, long size) => false; /// /// Adds a constant to a copy of this value and returns the result. Returns null if it's not supported. /// /// Opcode kind /// Value to add /// Size of a pointer in bytes /// public virtual ILValue? Add(AddOpCodeKind kind, long value, int pointerSize) => null; /// /// Subtracts a constant from a copy of this value and returns the result. Returns null if it's not supported. /// /// Opcode kind /// Value to subtract /// Size of a pointer in bytes /// public virtual ILValue? Sub(SubOpCodeKind kind, long value, int pointerSize) => null; /// /// Subtracts from a copy of this value and returns the result. Returns null if it's not supported. /// /// Opcode kind /// Value to subtract /// Size of a pointer in bytes /// public virtual ILValue? Sub(SubOpCodeKind kind, ILValue value, int pointerSize) => null; /// /// Converts this value to a new value. Returns null if it's not supported. /// /// Opcode kind /// public virtual ILValue? Conv(ConvOpCodeKind kind) => null; } /// /// Add opcode kind /// public enum AddOpCodeKind { /// /// Normal addition /// Add, /// /// Signed addition with an overflow check /// Add_Ovf, /// /// Unsigned addition with an overflow check /// Add_Ovf_Un, } /// /// Sub opcode kind /// public enum SubOpCodeKind { /// /// Normal subtraction /// Sub, /// /// Signed subtraction with an overflow check /// Sub_Ovf, /// /// Unsigned subtraction with an overflow check /// Sub_Ovf_Un, } /// /// Convert opcode kind /// public enum ConvOpCodeKind { /// /// Convert to a /// Conv_I, /// /// Convert to a , signed, overflow check /// Conv_Ovf_I, /// /// Convert to a , unsigned, overflow check /// Conv_Ovf_I_Un, /// /// Convert to a /// Conv_U, /// /// Convert to a , signed, overflow check /// Conv_Ovf_U, /// /// Convert to a , unsigned, overflow check /// Conv_Ovf_U_Un, } /// /// 32-bit integer. 1-byte, 2-byte and 4-byte integer values, booleans, and chars use this class. /// Smaller values are sign or zero extended. /// public class ConstantInt32ILValue : ILValue { /// /// Always returns /// public sealed override ILValueKind Kind => ILValueKind.Int32; /// /// Gets the value /// public int Value { get; } /// /// Gets the value as a /// public uint UnsignedValue => (uint)Value; internal ulong UnsignedValue64 => (ulong)(long)Value; /// /// Constructor /// /// AppDomain /// Value public ConstantInt32ILValue(DmdAppDomain appDomain, int value) { if (appDomain is null) throw new ArgumentNullException(nameof(appDomain)); Type = appDomain.System_Int32; Value = value; } /// /// Constructor /// /// Type, eg. /// Value public ConstantInt32ILValue(DmdType type, int value) { Type = type ?? throw new ArgumentNullException(nameof(type)); Value = value; } /// /// Gets the type of the value /// public override DmdType? Type { get; } /// /// ToString() /// /// public override string ToString() => "0x" + Value.ToString("X8"); } /// /// 64-bit integer /// public class ConstantInt64ILValue : ILValue { /// /// Always returns /// public sealed override ILValueKind Kind => ILValueKind.Int64; /// /// Gets the value /// public long Value { get; } /// /// Gets the value as a /// public ulong UnsignedValue => (ulong)Value; /// /// Constructor /// /// AppDomain /// Value public ConstantInt64ILValue(DmdAppDomain appDomain, long value) { if (appDomain is null) throw new ArgumentNullException(nameof(appDomain)); Type = appDomain.System_Int64; Value = value; } /// /// Constructor /// /// Type, eg. /// Value public ConstantInt64ILValue(DmdType type, long value) { Type = type ?? throw new ArgumentNullException(nameof(type)); Value = value; } /// /// Gets the type of the value /// public override DmdType? Type { get; } /// /// ToString() /// /// public override string ToString() => "0x" + Value.ToString("X16"); } /// /// 64-bit floating point value (32-bit floating point numbers are extended to 64 bits) /// public class ConstantFloatILValue : ILValue { /// /// Always returns /// public sealed override ILValueKind Kind => ILValueKind.Float; /// /// Gets the value /// public double Value { get; } /// /// Constructor /// /// AppDomain /// Value public ConstantFloatILValue(DmdAppDomain appDomain, double value) { if (appDomain is null) throw new ArgumentNullException(nameof(appDomain)); Type = appDomain.System_Double; Value = value; } /// /// Constructor /// /// Type, eg. /// Value public ConstantFloatILValue(DmdType type, double value) { Type = type ?? throw new ArgumentNullException(nameof(type)); Value = value; } /// /// Gets the type of the value /// public override DmdType? Type { get; } /// /// ToString() /// /// public override string ToString() => Value.ToString(); } /// /// native integer or unmanaged pointer /// public abstract class NativeIntILValue : ILValue { /// /// Always returns /// public sealed override ILValueKind Kind => ILValueKind.NativeInt; } /// /// native integer or unmanaged pointer /// public class ConstantNativeIntILValue : NativeIntILValue { readonly long value; /// /// Gets the value as a /// public int Value32 => (int)value; /// /// Gets the value as a /// public long Value64 => value; /// /// Gets the value as a /// public uint UnsignedValue32 => (uint)value; /// /// Gets the value as a /// public ulong UnsignedValue64 => (ulong)value; /// /// Creates a 32-bit native int /// /// AppDomain /// Value /// public static ConstantNativeIntILValue Create32(DmdAppDomain appDomain, int value) => new ConstantNativeIntILValue(appDomain, value); /// /// Creates a 64-bit native int /// /// AppDomain /// Value /// public static ConstantNativeIntILValue Create64(DmdAppDomain appDomain, long value) => new ConstantNativeIntILValue(appDomain, value); /// /// Creates a 32-bit native int /// /// Type, eg. /// Value /// public static ConstantNativeIntILValue Create32(DmdType type, int value) => new ConstantNativeIntILValue(type, value); /// /// Creates a 64-bit native int /// /// Type, eg. /// Value /// public static ConstantNativeIntILValue Create64(DmdType type, long value) => new ConstantNativeIntILValue(type, value); /// /// Constructor /// /// AppDomain /// Value protected ConstantNativeIntILValue(DmdAppDomain appDomain, int value) { if (appDomain is null) throw new ArgumentNullException(nameof(appDomain)); Type = appDomain.System_IntPtr; this.value = value; } /// /// Constructor /// /// AppDomain /// Value protected ConstantNativeIntILValue(DmdAppDomain appDomain, long value) { if (appDomain is null) throw new ArgumentNullException(nameof(appDomain)); Type = appDomain.System_IntPtr; this.value = value; } /// /// Constructor /// /// Type, eg. /// Value protected ConstantNativeIntILValue(DmdType type, int value) { Type = type ?? throw new ArgumentNullException(nameof(type)); this.value = value; } /// /// Constructor /// /// Type, eg. /// Value protected ConstantNativeIntILValue(DmdType type, long value) { Type = type ?? throw new ArgumentNullException(nameof(type)); this.value = value; } /// /// Gets the type of the value /// public override DmdType? Type { get; } /// /// ToString() /// /// public override string ToString() => "0x" + value.ToString("X"); } /// /// Function pointer, created by the ldftn/ldvirtftn instructions /// public sealed class FunctionPointerILValue : NativeIntILValue { /// /// true if it was created by a ldvirtftn instruction, false it was created by a ldftn instruction /// public bool IsVirtual => VirtualThisObject is not null; /// /// Gets the this value if and only if this was created by a ldvirtftn instruction, otherwise it's null /// public ILValue? VirtualThisObject { get; } /// /// Gets the method /// public DmdMethodBase Method { get; } /// /// Constructor (used by ldftn instruction) /// /// Method public FunctionPointerILValue(DmdMethodBase method) { Method = method ?? throw new ArgumentNullException(nameof(method)); Type = method.AppDomain.System_Void.MakePointerType(); } /// /// Constructor (used by ldvirtftn instruction) /// /// Method /// This object public FunctionPointerILValue(DmdMethodBase method, ILValue thisValue) { Method = method; Type = method.AppDomain.System_Void.MakePointerType(); VirtualThisObject = thisValue; } /// /// Gets the type of the value /// public override DmdType? Type { get; } } /// /// Pointer to a block of memory. Used by eg. localloc /// public sealed class NativeMemoryILValue : NativeIntILValue { readonly byte[] data; long offset; int Offset32 => (int)offset; long Offset64 => offset; uint UnsignedOffset32 => (uint)offset; ulong UnsignedOffset64 => (ulong)offset; /// /// Constructor /// /// AppDomain /// Size of memory public NativeMemoryILValue(DmdAppDomain appDomain, int size) { if (appDomain is null) throw new ArgumentNullException(nameof(appDomain)); Type = appDomain.System_Void.MakePointerType(); data = new byte[size]; } NativeMemoryILValue(byte[] data, long offset) { this.data = data; this.offset = offset; } /// /// Adds a constant to a copy of this value and returns the result. Returns null if it's not supported. /// /// Opcode kind /// Value to add /// Size of a pointer in bytes /// public override ILValue? Add(AddOpCodeKind kind, long value, int pointerSize) { if (value == 0) return this; switch (kind) { case AddOpCodeKind.Add: if (pointerSize == 4) return new NativeMemoryILValue(data, Offset32 + (int)value); return new NativeMemoryILValue(data, Offset64 + value); case AddOpCodeKind.Add_Ovf: if (pointerSize == 4) { int value2 = (int)value; return new NativeMemoryILValue(data, checked(Offset32 + value2)); } return new NativeMemoryILValue(data, checked(Offset64 + value)); case AddOpCodeKind.Add_Ovf_Un: if (pointerSize == 4) { uint value2 = (uint)value; return new NativeMemoryILValue(data, (int)checked(UnsignedOffset32 + value2)); } else { ulong value2 = (ulong)value; return new NativeMemoryILValue(data, (long)checked(UnsignedOffset64 + value2)); } default: throw new InvalidOperationException(); } } /// /// Subtracts a constant from a copy of this value and returns the result. Returns null if it's not supported. /// /// Opcode kind /// Value to subtract /// Size of a pointer in bytes /// public override ILValue? Sub(SubOpCodeKind kind, long value, int pointerSize) { if (value == 0) return this; switch (kind) { case SubOpCodeKind.Sub: if (pointerSize == 4) return new NativeMemoryILValue(data, Offset32 - (int)value); return new NativeMemoryILValue(data, Offset64 - value); case SubOpCodeKind.Sub_Ovf: if (pointerSize == 4) { int value2 = (int)value; return new NativeMemoryILValue(data, checked(Offset32 - value2)); } return new NativeMemoryILValue(data, checked(Offset64 - value)); case SubOpCodeKind.Sub_Ovf_Un: if (pointerSize == 4) { uint value2 = (uint)value; return new NativeMemoryILValue(data, (int)checked(UnsignedOffset32 - value2)); } else { ulong value2 = (ulong)value; return new NativeMemoryILValue(data, (long)checked(UnsignedOffset64 - value2)); } default: throw new InvalidOperationException(); } } /// /// Loads a value. Returns null if it's not supported. /// /// Type /// Type of value to load /// public override ILValue? LoadIndirect(DmdType type, LoadValueType loadValueType) { int pointerSize = type.AppDomain.Runtime.PointerSize; switch (loadValueType) { case LoadValueType.I: if (pointerSize == 4) { if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return null; return ConstantNativeIntILValue.Create32(type.AppDomain, BitConverter.ToInt32(data, (int)offset)); } else { Debug.Assert(pointerSize == 8); if (offset + 8 - 1 < offset || (ulong)offset + 8 - 1 >= (ulong)data.Length) return null; return ConstantNativeIntILValue.Create64(type.AppDomain, BitConverter.ToInt64(data, (int)offset)); } case LoadValueType.I1: if ((ulong)offset >= (ulong)data.Length) return null; return new ConstantInt32ILValue(type.AppDomain.System_SByte, (sbyte)data[(int)offset]); case LoadValueType.I2: if (offset + 2 - 1 < offset || (ulong)offset + 2 - 1 >= (ulong)data.Length) return null; return new ConstantInt32ILValue(type.AppDomain.System_Int16, BitConverter.ToInt16(data, (int)offset)); case LoadValueType.I4: if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return null; return new ConstantInt32ILValue(type.AppDomain.System_Int32, BitConverter.ToInt32(data, (int)offset)); case LoadValueType.I8: if (offset + 8 - 1 < offset || (ulong)offset + 8 - 1 >= (ulong)data.Length) return null; return new ConstantInt64ILValue(type.AppDomain.System_Int64, BitConverter.ToInt64(data, (int)offset)); case LoadValueType.R4: if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return null; return new ConstantFloatILValue(type.AppDomain.System_Single, BitConverter.ToSingle(data, (int)offset)); case LoadValueType.R8: if (offset + 8 - 1 < offset || (ulong)offset + 8 - 1 >= (ulong)data.Length) return null; return new ConstantFloatILValue(type.AppDomain.System_Double, BitConverter.ToDouble(data, (int)offset)); case LoadValueType.Ref: return null; case LoadValueType.U1: if ((ulong)offset >= (ulong)data.Length) return null; return new ConstantInt32ILValue(type.AppDomain.System_Byte, data[(int)offset]); case LoadValueType.U2: if (offset + 2 - 1 < offset || (ulong)offset + 2 - 1 >= (ulong)data.Length) return null; return new ConstantInt32ILValue(type.AppDomain.System_UInt16, BitConverter.ToUInt16(data, (int)offset)); case LoadValueType.U4: if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return null; return new ConstantInt32ILValue(type.AppDomain.System_UInt32, BitConverter.ToInt32(data, (int)offset)); default: return null; } } /// /// Stores a value. Returns false if it's not supported. /// /// Type /// Type of value to store /// Value /// public override bool StoreIndirect(DmdType type, LoadValueType loadValueType, ILValue value) { int pointerSize = type.AppDomain.Runtime.PointerSize; long v; double d; switch (loadValueType) { case LoadValueType.I: if (pointerSize == 4) { if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, pointerSize, out v)) return false; WriteInt32(data, (int)offset, v); return true; } else { Debug.Assert(pointerSize == 8); if (offset + 8 - 1 < offset || (ulong)offset + 8 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, pointerSize, out v)) return false; WriteInt64(data, (int)offset, v); return true; } case LoadValueType.I1: case LoadValueType.U1: if ((ulong)offset >= (ulong)data.Length) return false; if (!GetValue(value, pointerSize, out v)) return false; WriteInt8(data, (int)offset, v); return true; case LoadValueType.I2: case LoadValueType.U2: if (offset + 2 - 1 < offset || (ulong)offset + 2 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, pointerSize, out v)) return false; WriteInt16(data, (int)offset, v); return true; case LoadValueType.I4: case LoadValueType.U4: if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, pointerSize, out v)) return false; WriteInt32(data, (int)offset, v); return true; case LoadValueType.I8: if (offset + 8 - 1 < offset || (ulong)offset + 8 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, pointerSize, out v)) return false; WriteInt64(data, (int)offset, v); return true; case LoadValueType.R4: if (offset + 4 - 1 < offset || (ulong)offset + 4 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, out d)) return false; WriteSingle(data, (int)offset, (float)d); return true; case LoadValueType.R8: if (offset + 8 - 1 < offset || (ulong)offset + 8 - 1 >= (ulong)data.Length) return false; if (!GetValue(value, out d)) return false; WriteDouble(data, (int)offset, d); return true; case LoadValueType.Ref: return false; default: return false; } } static bool GetValue(ILValue value, int pointerSize, out long result) { if (value is ConstantInt32ILValue c32) { result = c32.Value; return true; } else if (value is ConstantInt64ILValue c64) { result = c64.Value; return true; } else if (value is ConstantNativeIntILValue cni) { result = pointerSize == 4 ? cni.Value32 : cni.Value64; return true; } result = 0; return false; } static bool GetValue(ILValue value, out double result) { if (value is ConstantFloatILValue f) { result = f.Value; return true; } result = 0; return false; } static void WriteInt8(byte[] data, int offset, long value) { data[offset] = (byte)value; } static void WriteInt16(byte[] data, int offset, long value) { data[offset++] = (byte)value; data[offset] = (byte)(value >> 8); } static void WriteInt32(byte[] data, int offset, long value) { data[offset++] = (byte)value; data[offset++] = (byte)(value >> 8); data[offset++] = (byte)(value >> 16); data[offset] = (byte)(value >> 24); } static void WriteInt64(byte[] data, int offset, long value) { data[offset++] = (byte)value; data[offset++] = (byte)(value >> 8); data[offset++] = (byte)(value >> 16); data[offset++] = (byte)(value >> 24); data[offset++] = (byte)(value >> 32); data[offset++] = (byte)(value >> 40); data[offset++] = (byte)(value >> 48); data[offset] = (byte)(value >> 56); } static void WriteSingle(byte[] data, int offset, float value) { var b = BitConverter.GetBytes((float)value); for (int i = 0; i < b.Length; i++) data[offset + i] = b[i]; } static void WriteDouble(byte[] data, int offset, double value) { var b = BitConverter.GetBytes(value); for (int i = 0; i < b.Length; i++) data[offset + i] = b[i]; } /// /// Initializes memory or returns false if it's not supported /// /// Value to write /// Size of data /// public override bool InitializeMemory(byte value, long size) { if (offset + size < offset || (ulong)(offset + size) > (ulong)data.Length) return false; int size2 = (int)size; int o = (int)offset; var d = data; for (int i = 0; i < size2; i++) d[i + o] = value; return true; } /// /// Gets the type of the value /// public override DmdType? Type { get; } } /// /// Managed pointer /// public abstract class ByRefILValue : ILValue { /// /// Always returns /// public sealed override ILValueKind Kind => ILValueKind.ByRef; } /// /// A reference type or a value type /// public abstract class TypeILValue : ILValue { /// /// Always returns /// public sealed override ILValueKind Kind => ILValueKind.Type; } /// /// A null reference /// public class NullObjectRefILValue : TypeILValue { /// /// Returns true since it's a null value /// public sealed override bool IsNull => true; /// /// Constructor /// public NullObjectRefILValue() { } /// /// Gets the type of the value /// public override DmdType? Type => null; } }