326 lines
12 KiB
C#
Raw Permalink Normal View History

2021-09-20 18:20:01 +02:00
/*
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.Diagnostics.CodeAnalysis;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.Engine.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Debugger.DotNet.Metadata;
using Mono.Debugger.Soft;
namespace dnSpy.Debugger.DotNet.Mono.Impl.Evaluation {
sealed class DbgDotNetValueImpl : DbgDotNetValue {
public override DmdType Type { get; }
public override bool IsNull => (flags & ValueFlags.IsNull) != 0;
bool IsNullByRef => (flags & ValueFlags.IsNullByRef) != 0;
[Flags]
enum ValueFlags : byte {
None = 0,
IsNull = 0x01,
IsNullByRef = 0x02,
}
internal ValueLocation ValueLocation => valueLocation;
internal Value Value => value;
readonly DbgEngineImpl engine;
readonly ValueLocation valueLocation;
readonly Value value;
readonly DbgDotNetRawValue rawValue;
readonly ValueFlags flags;
public DbgDotNetValueImpl(DbgEngineImpl engine, ValueLocation valueLocation, Value value) {
this.engine = engine ?? throw new ArgumentNullException(nameof(engine));
this.valueLocation = valueLocation ?? throw new ArgumentNullException(nameof(valueLocation));
this.value = value ?? throw new ArgumentNullException(nameof(value));
Type = MonoValueTypeCreator.CreateType(engine, value, valueLocation.Type);
rawValue = new DbgDotNetRawValueFactory(engine).Create(value, Type);
var flags = ValueFlags.None;
if (value is PrimitiveValue pv && (pv.Value is null || ((Type.IsPointer || Type.IsFunctionPointer) && boxed0L.Equals(pv.Value)))) {
if (Type.IsByRef)
flags |= ValueFlags.IsNullByRef;
else
flags |= ValueFlags.IsNull;
}
this.flags = flags;
}
static readonly object boxed0L = 0L;
public override IDbgDotNetRuntime? TryGetDotNetRuntime() => engine.DotNetRuntime;
public override DbgDotNetValueResult LoadIndirect() {
if (!Type.IsByRef)
return base.LoadIndirect();
if (IsNullByRef)
return DbgDotNetValueResult.Create(new SyntheticNullValue(Type.GetElementType()!));
if (engine.CheckMonoDebugThread())
return Dereference_MonoDebug();
return engine.InvokeMonoDebugThread(() => Dereference_MonoDebug());
}
DbgDotNetValueResult Dereference_MonoDebug() {
Debug.Assert(Type.IsByRef && !IsNullByRef);
engine.VerifyMonoDebugThread();
return DbgDotNetValueResult.Create(engine.CreateDotNetValue_MonoDebug(valueLocation.Dereference()));
}
public override string? StoreIndirect(DbgEvaluationInfo evalInfo, object? value) {
if (!Type.IsByRef)
return PredefinedEvaluationErrorMessages.InternalDebuggerError;
if (engine.CheckMonoDebugThread())
return StoreIndirect_MonoDebug(evalInfo, value);
return engine.InvokeMonoDebugThread(() => StoreIndirect_MonoDebug(evalInfo, value));
}
string? StoreIndirect_MonoDebug(DbgEvaluationInfo evalInfo, object? value) {
engine.VerifyMonoDebugThread();
evalInfo.CancellationToken.ThrowIfCancellationRequested();
if (!Type.IsByRef)
return PredefinedEvaluationErrorMessages.InternalDebuggerError;
var res = engine.CreateMonoValue_MonoDebug(evalInfo, value, Type.GetElementType()!);
if (res.ErrorMessage is not null)
return res.ErrorMessage;
return valueLocation.Store(res.Value!);
}
public override bool GetArrayCount(out uint elementCount) {
if (Type.IsArray) {
if (engine.CheckMonoDebugThread()) {
elementCount = GetArrayCountCore_MonoDebug();
return true;
}
else {
elementCount = engine.InvokeMonoDebugThread(() => GetArrayCountCore_MonoDebug());
return true;
}
}
elementCount = 0;
return false;
}
uint GetArrayCountCore_MonoDebug() {
Debug.Assert(Type.IsArray);
engine.VerifyMonoDebugThread();
var arrayMirror = value as ArrayMirror;
if (arrayMirror is null)
return 0;
return (uint)arrayMirror.Length;
}
public override bool GetArrayInfo(out uint elementCount, [NotNullWhen(true)] out DbgDotNetArrayDimensionInfo[]? dimensionInfos) {
if (Type.IsArray) {
if (engine.CheckMonoDebugThread())
return GetArrayInfo_MonoDebug(out elementCount, out dimensionInfos);
else {
uint tmpElementCount = 0;
DbgDotNetArrayDimensionInfo[]? tmpDimensionInfos = null;
bool res = engine.InvokeMonoDebugThread(() => GetArrayInfo_MonoDebug(out tmpElementCount, out tmpDimensionInfos));
elementCount = tmpElementCount;
dimensionInfos = tmpDimensionInfos!;
return res;
}
}
elementCount = 0;
dimensionInfos = null;
return false;
}
bool GetArrayInfo_MonoDebug(out uint elementCount, [NotNullWhen(true)] out DbgDotNetArrayDimensionInfo[]? dimensionInfos) {
Debug.Assert(Type.IsArray);
engine.VerifyMonoDebugThread();
var arrayMirror = value as ArrayMirror;
if (arrayMirror is null) {
elementCount = 0;
dimensionInfos = null;
return false;
}
elementCount = (uint)arrayMirror.Length;
var infos = new DbgDotNetArrayDimensionInfo[arrayMirror.Rank];
for (int i = 0; i < infos.Length; i++)
infos[i] = new DbgDotNetArrayDimensionInfo(arrayMirror.GetLowerBound(i), (uint)arrayMirror.GetLength(i));
dimensionInfos = infos;
return true;
}
public override DbgDotNetValueResult GetArrayElementAt(uint index) {
if (!Type.IsArray)
return base.GetArrayElementAt(index);
if (engine.CheckMonoDebugThread())
return GetArrayElementAt_MonoDebug(index);
return engine.InvokeMonoDebugThread(() => GetArrayElementAt_MonoDebug(index));
}
DbgDotNetValueResult GetArrayElementAt_MonoDebug(uint index) {
Debug.Assert(Type.IsArray);
engine.VerifyMonoDebugThread();
var info = GetArrayElementValueLocation_MonoDebug(index);
if (info.errorMessage is not null)
return DbgDotNetValueResult.CreateError(info.errorMessage);
return DbgDotNetValueResult.Create(engine.CreateDotNetValue_MonoDebug(info.valueLocation!));
}
(ArrayElementValueLocation? valueLocation, string? errorMessage) GetArrayElementValueLocation_MonoDebug(uint index) {
Debug.Assert(Type.IsArray);
engine.VerifyMonoDebugThread();
var arrayMirror = value as ArrayMirror;
if (arrayMirror is null)
return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
return (new ArrayElementValueLocation(Type.GetElementType()!, arrayMirror, index), null);
}
public override string? SetArrayElementAt(DbgEvaluationInfo evalInfo, uint index, object? value) {
if (!Type.IsArray)
return base.SetArrayElementAt(evalInfo, index, value);
if (engine.CheckMonoDebugThread())
return SetArrayElementAt_MonoDebug(evalInfo, index, value);
return engine.InvokeMonoDebugThread(() => SetArrayElementAt_MonoDebug(evalInfo, index, value));
}
string? SetArrayElementAt_MonoDebug(DbgEvaluationInfo evalInfo, uint index, object? value) {
engine.VerifyMonoDebugThread();
evalInfo.CancellationToken.ThrowIfCancellationRequested();
var info = GetArrayElementValueLocation_MonoDebug(index);
if (info.errorMessage is not null)
return info.errorMessage;
var res = engine.CreateMonoValue_MonoDebug(evalInfo, value, info.valueLocation!.Type);
if (res.ErrorMessage is not null)
return res.ErrorMessage;
return info.valueLocation.Store(res.Value!);
}
public override DbgDotNetValueResult? Box(DbgEvaluationInfo evalInfo) {
if (engine.CheckMonoDebugThread())
return Box_MonoDebug(evalInfo);
return engine.InvokeMonoDebugThread(() => Box_MonoDebug(evalInfo));
}
DbgDotNetValueResult? Box_MonoDebug(DbgEvaluationInfo evalInfo) {
engine.VerifyMonoDebugThread();
evalInfo.CancellationToken.ThrowIfCancellationRequested();
if (!Type.IsValueType)
return DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError);
var value = this.value;
// Even if it's boxed, box the unboxed value. This code path should only be called if
// the compiler thinks it's an unboxed value, so we must make a new boxed value.
if (value is ObjectMirror)
value = ValueUtils.Unbox((ObjectMirror)value, Type);
return engine.Box_MonoDebug(evalInfo, value, Type);
}
public override DbgRawAddressValue? GetRawAddressValue(bool onlyDataAddress) {
if (engine.CheckMonoDebugThread())
return GetRawAddressValue_MonoDebug(onlyDataAddress);
return engine.InvokeMonoDebugThread(() => GetRawAddressValue_MonoDebug(onlyDataAddress));
}
DbgRawAddressValue? GetRawAddressValue_MonoDebug(bool onlyDataAddress) {
engine.VerifyMonoDebugThread();
if (IsNull || IsNullByRef || Type.IsByRef)
return null;
ulong addr;
switch (value) {
case ArrayMirror am:
addr = (ulong)am.Address;
if (addr == 0)
return null;
var dataAddr = GetArrayAddress(am, Type.GetElementType()!, engine);
if (onlyDataAddress || dataAddr is null)
return dataAddr ?? new DbgRawAddressValue(addr, 0);
var offsetToArrayData = engine.OffsetToArrayData;
if (offsetToArrayData is null)
return new DbgRawAddressValue(addr, 0);
return new DbgRawAddressValue(addr, dataAddr.Value.Length + (uint)offsetToArrayData.Value);
case StringMirror sm:
addr = (ulong)sm.Address;
if (addr == 0)
return null;
var s = rawValue.RawValue as string;
if (s is null)
return null;
var offsetToStringData = engine.OffsetToStringData;
if (offsetToStringData is null)
return new DbgRawAddressValue(addr, 0);
if (onlyDataAddress)
return new DbgRawAddressValue(addr + (uint)offsetToStringData.Value, (uint)s.Length * 2);
return new DbgRawAddressValue(addr, (uint)offsetToStringData.Value + (uint)s.Length * 2);
case ObjectMirror om:
addr = (ulong)om.Address;
if (addr == 0)
return null;
return new DbgRawAddressValue(addr, 0);
default:
return null;
}
}
internal static DbgRawAddressValue? GetArrayAddress(ArrayMirror v, DmdType elementType, DbgEngineImpl engine) {
var offsetToArrayData = engine.OffsetToArrayData;
if (offsetToArrayData is null)
return null;
var addr = (ulong)v.Address;
if (addr == 0)
return null;
var arrayCount = v.Length;
var startAddr = addr + (uint)offsetToArrayData.Value;
if (!TryGetSize(elementType, out var elemSize))
return new DbgRawAddressValue(startAddr, 0);
ulong totalSize = (uint)elemSize * (ulong)(uint)arrayCount;
return new DbgRawAddressValue(startAddr, totalSize);
}
static bool TryGetSize(DmdType type, out int size) {
if (!type.IsValueType || type.IsPointer || type.IsFunctionPointer || type == type.AppDomain.System_IntPtr || type == type.AppDomain.System_UIntPtr) {
size = type.AppDomain.Runtime.PointerSize;
return true;
}
switch (DmdType.GetTypeCode(type)) {
case TypeCode.Boolean: size = 1; return true;
case TypeCode.Char: size = 2; return true;
case TypeCode.SByte: size = 1; return true;
case TypeCode.Byte: size = 1; return true;
case TypeCode.Int16: size = 2; return true;
case TypeCode.UInt16: size = 2; return true;
case TypeCode.Int32: size = 4; return true;
case TypeCode.UInt32: size = 4; return true;
case TypeCode.Int64: size = 8; return true;
case TypeCode.UInt64: size = 8; return true;
case TypeCode.Single: size = 4; return true;
case TypeCode.Double: size = 8; return true;
default: size = 0; return false;
}
}
public override DbgDotNetRawValue GetRawValue() => rawValue;
public override void Dispose() { }
}
}