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

422 lines
15 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using dndbg.COM.CorDebug;
using dndbg.Engine;
using dnSpy.Contracts.Debugger.DotNet.Evaluation;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Debugger.DotNet.CorDebug.CallStack;
using dnSpy.Debugger.DotNet.Metadata;
namespace dnSpy.Debugger.DotNet.CorDebug.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 DbgCorValueHolder CorValueHolder => value;
readonly DbgEngineImpl engine;
readonly DbgCorValueHolder value;
readonly DbgDotNetRawValue rawValue;
readonly ValueFlags flags;
volatile int disposed;
public DbgDotNetValueImpl(DbgEngineImpl engine, DbgCorValueHolder value) {
this.engine = engine ?? throw new ArgumentNullException(nameof(engine));
this.value = value ?? throw new ArgumentNullException(nameof(value));
Type = value.Type;
var corValue = value.CorValue;
rawValue = new DbgDotNetRawValueFactory(engine).Create(corValue, Type);
var flags = ValueFlags.None;
if (corValue.IsNull) {
if (Type.IsByRef)
flags |= ValueFlags.IsNullByRef;
else
flags |= ValueFlags.IsNull;
}
this.flags = flags;
}
public override IDbgDotNetRuntime? TryGetDotNetRuntime() => engine.DotNetRuntime;
internal CorValue? TryGetCorValue() {
try {
return value.CorValue;
}
catch (ObjectDisposedException) {
return null;
}
}
public override DbgDotNetValueResult LoadIndirect() {
if (!Type.IsByRef && !Type.IsPointer)
return base.LoadIndirect();
if (IsNullByRef || IsNull)
return DbgDotNetValueResult.Create(new SyntheticNullValue(Type.GetElementType()!));
if (engine.CheckCorDebugThread())
return Dereference_CorDebug();
return engine.InvokeCorDebugThread(() => Dereference_CorDebug());
}
DbgDotNetValueResult Dereference_CorDebug() {
Debug.Assert((Type.IsByRef && !IsNullByRef) || (Type.IsPointer && !IsNull));
engine.VerifyCorDebugThread();
int hr = -1;
var dereferencedValue = TryGetCorValue()?.GetDereferencedValue(out hr);
// We sometimes get 0x80131C49 = CORDBG_E_READVIRTUAL_FAILURE
if (dereferencedValue is null)
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.GetErrorMessage(hr));
return DbgDotNetValueResult.Create(engine.CreateDotNetValue_CorDebug(dereferencedValue, Type.AppDomain, tryCreateStrongHandle: true));
}
public override string? StoreIndirect(DbgEvaluationInfo evalInfo, object? value) {
if (!Type.IsByRef && !Type.IsPointer)
return CordbgErrorHelper.InternalError;
if (IsNull)
return CordbgErrorHelper.InternalError;
if (engine.CheckCorDebugThread())
return StoreIndirect_CorDebug(evalInfo, value);
return engine.InvokeCorDebugThread(() => StoreIndirect_CorDebug(evalInfo, value));
}
string? StoreIndirect_CorDebug(DbgEvaluationInfo evalInfo, object? value) {
engine.VerifyCorDebugThread();
evalInfo.CancellationToken.ThrowIfCancellationRequested();
if (!ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame))
return CordbgErrorHelper.InternalError;
if (!Type.IsByRef && !Type.IsPointer)
return CordbgErrorHelper.InternalError;
Func<CreateCorValueResult> createTargetValue = () => {
var objValue = TryGetCorValue();
if (objValue is null)
return new CreateCorValueResult(null, -1);
Debug.Assert(objValue.ElementType == CorElementType.ByRef || objValue.ElementType == CorElementType.Ptr);
if (objValue.ElementType == CorElementType.ByRef || objValue.ElementType == CorElementType.Ptr) {
var derefencedValue = objValue.GetDereferencedValue(out int hr);
if (derefencedValue is null)
return new CreateCorValueResult(null, hr);
if (!derefencedValue.IsReference) {
if (derefencedValue.IsGeneric)
return new CreateCorValueResult(derefencedValue, 0, canDispose: true);
engine.DisposeHandle_CorDebug(derefencedValue);
return new CreateCorValueResult(null, -1);
}
return new CreateCorValueResult(derefencedValue, 0, canDispose: true);
}
else
return new CreateCorValueResult(null, -1);
};
return engine.StoreValue_CorDebug(evalInfo, ilFrame, createTargetValue, Type.GetElementType()!, value);
}
readonly struct ArrayObjectValue : IDisposable {
readonly DbgEngineImpl engine;
public readonly CorValue? Value;
readonly bool ownsValue;
public ArrayObjectValue(DbgEngineImpl engine, CorValue value) {
this.engine = engine;
Debug.Assert(!value.IsNull);
if (value.IsReference) {
Value = value.GetDereferencedValue(out int hr);
ownsValue = true;
}
else {
Value = value;
ownsValue = false;
}
// Value is sometimes null, DereferencedValue can fail with 0x80131305 = CORDBG_E_BAD_REFERENCE_VALUE
Debug2.Assert(Value is null || Value.IsArray);
}
public void Dispose() {
if (ownsValue)
engine.DisposeHandle_CorDebug(Value);
}
}
public override bool GetArrayCount(out uint elementCount) {
if (Type.IsArray) {
if (engine.CheckCorDebugThread()) {
elementCount = GetArrayCountCore_CorDebug();
return true;
}
else {
elementCount = engine.InvokeCorDebugThread(() => GetArrayCountCore_CorDebug());
return true;
}
}
elementCount = 0;
return false;
}
uint GetArrayCountCore_CorDebug() {
Debug.Assert(Type.IsArray);
engine.VerifyCorDebugThread();
var corValue = TryGetCorValue();
if (corValue is null || corValue.IsNull)
return 0;
using (var obj = new ArrayObjectValue(engine, corValue))
return obj.Value?.ArrayCount ?? 0;
}
public override bool GetArrayInfo(out uint elementCount, [NotNullWhen(true)] out DbgDotNetArrayDimensionInfo[]? dimensionInfos) {
if (Type.IsArray) {
if (engine.CheckCorDebugThread())
return GetArrayInfo_CorDebug(out elementCount, out dimensionInfos);
else {
uint tmpElementCount = 0;
DbgDotNetArrayDimensionInfo[]? tmpDimensionInfos = null;
bool res = engine.InvokeCorDebugThread(() => GetArrayInfo_CorDebug(out tmpElementCount, out tmpDimensionInfos));
elementCount = tmpElementCount;
dimensionInfos = tmpDimensionInfos!;
return res;
}
}
elementCount = 0;
dimensionInfos = null;
return false;
}
bool GetArrayInfo_CorDebug(out uint elementCount, [NotNullWhen(true)] out DbgDotNetArrayDimensionInfo[]? dimensionInfos) {
Debug.Assert(Type.IsArray);
engine.VerifyCorDebugThread();
var corValue = TryGetCorValue();
if (corValue is null || corValue.IsNull) {
elementCount = 0;
dimensionInfos = null;
return false;
}
using (var obj = new ArrayObjectValue(engine, corValue)) {
if (obj.Value is null) {
elementCount = 0;
dimensionInfos = null;
return false;
}
elementCount = obj.Value.ArrayCount;
var baseIndexes = (obj.Value.HasBaseIndicies ? obj.Value.BaseIndicies : null) ?? Array.Empty<uint>();
var dimensions = obj.Value.Dimensions;
if (dimensions is not null) {
var infos = new DbgDotNetArrayDimensionInfo[dimensions.Length];
for (int i = 0; i < infos.Length; i++)
infos[i] = new DbgDotNetArrayDimensionInfo((int)(i < baseIndexes.Length ? baseIndexes[i] : 0), dimensions[i]);
dimensionInfos = infos;
return true;
}
else {
dimensionInfos = null;
return false;
}
}
}
public override DbgDotNetValueResult GetArrayElementAt(uint index) {
if (!Type.IsArray)
return base.GetArrayElementAt(index);
if (engine.CheckCorDebugThread())
return GetArrayElementAt_CorDebug(index);
return engine.InvokeCorDebugThread(() => GetArrayElementAt_CorDebug(index));
}
DbgDotNetValueResult GetArrayElementAt_CorDebug(uint index) {
Debug.Assert(Type.IsArray);
engine.VerifyCorDebugThread();
var corValue = TryGetCorValue();
if (corValue is null || corValue.IsNull)
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.InternalError);
using (var obj = new ArrayObjectValue(engine, corValue)) {
int hr = -1;
var elemValue = obj.Value?.GetElementAtPosition(index, out hr);
if (elemValue is null)
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.GetErrorMessage(hr));
return DbgDotNetValueResult.Create(engine.CreateDotNetValue_CorDebug(elemValue, Type.AppDomain, tryCreateStrongHandle: true));
}
}
public override string? SetArrayElementAt(DbgEvaluationInfo evalInfo, uint index, object? value) {
if (!Type.IsArray)
return base.SetArrayElementAt(evalInfo, index, value);
if (engine.CheckCorDebugThread())
return SetArrayElementAt_CorDebug(evalInfo, index, value);
return engine.InvokeCorDebugThread(() => SetArrayElementAt_CorDebug(evalInfo, index, value));
}
string? SetArrayElementAt_CorDebug(DbgEvaluationInfo evalInfo, uint index, object? value) {
engine.VerifyCorDebugThread();
evalInfo.CancellationToken.ThrowIfCancellationRequested();
if (!ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame))
return CordbgErrorHelper.InternalError;
Func<CreateCorValueResult> createTargetValue = () => {
var corValue = TryGetCorValue();
if (corValue is null || corValue.IsNull)
return new CreateCorValueResult(null, -1);
using (var obj = new ArrayObjectValue(engine, corValue)) {
if (obj.Value is null)
return new CreateCorValueResult(null, -1);
var elemValue = obj.Value.GetElementAtPosition(index, out int hr);
return new CreateCorValueResult(elemValue, hr);
}
};
return engine.StoreValue_CorDebug(evalInfo, ilFrame, createTargetValue, Type.GetElementType()!, value);
}
public override DbgDotNetValueResult? Box(DbgEvaluationInfo evalInfo) {
if (engine.CheckCorDebugThread())
return Box_CorDebug(evalInfo);
return engine.InvokeCorDebugThread(() => Box_CorDebug(evalInfo));
}
DbgDotNetValueResult? Box_CorDebug(DbgEvaluationInfo evalInfo) {
engine.VerifyCorDebugThread();
evalInfo.CancellationToken.ThrowIfCancellationRequested();
var corValue = TryGetCorValue();
if (corValue is null)
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.InternalError);
if (!ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame))
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.InternalError);
// 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 (corValue.IsReference) {
corValue = corValue.GetDereferencedValue(out int hr);
if (corValue is null)
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.GetErrorMessage(hr));
}
if (corValue.IsBox) {
corValue = corValue.GetBoxedValue(out int hr);
if (corValue is null)
return DbgDotNetValueResult.CreateError(CordbgErrorHelper.GetErrorMessage(hr));
}
return engine.Box_CorDebug(evalInfo, ilFrame.GetCorAppDomain(), corValue, Type);
}
public override DbgRawAddressValue? GetRawAddressValue(bool onlyDataAddress) {
if (engine.CheckCorDebugThread())
return GetRawAddressValue_CorDebug(onlyDataAddress);
return engine.InvokeCorDebugThread(() => GetRawAddressValue_CorDebug(onlyDataAddress));
}
// See System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData
const uint OffsetToStringData32_CLR2 = 12;
const uint OffsetToStringData64_CLR2 = 16;
// CLR 4 and .NET Core 1.0 - 2.0
const uint OffsetToStringData32 = 8;
const uint OffsetToStringData64 = 12;
DbgRawAddressValue? GetRawAddressValue_CorDebug(bool onlyDataAddress) {
engine.VerifyCorDebugThread();
var v = TryGetCorValue();
if (v is null)
return null;
if (Type.IsByRef) {
if (IsNullByRef)
return null;
return new DbgRawAddressValue(v.Address, (uint)Type.AppDomain.Runtime.PointerSize);
}
if (IsNull)
return null;
if (v.IsNull)
return null;
if (v.IsReference) {
if (v.ElementType == CorElementType.Ptr || v.ElementType == CorElementType.FnPtr)
return null;
v = v.GetDereferencedValue(out int hr);
if (v is null)
return null;
}
if (v.IsBox) {
v = v.GetBoxedValue(out int hr);
if (v is null)
return null;
}
var addr = v.Address;
var size = v.Size;
if (addr == 0)
return null;
var etype = v.ElementType;
if (!onlyDataAddress || v.IsValueClass || (CorElementType.Boolean <= etype && etype <= CorElementType.R8) || etype == CorElementType.I || etype == CorElementType.U)
return new DbgRawAddressValue(addr, size);
switch (etype) {
case CorElementType.String:
uint offsetToStringData;
if (engine.DebuggeeVersion.StartsWith("v2."))
offsetToStringData = IntPtr.Size == 4 ? OffsetToStringData32_CLR2 : OffsetToStringData64_CLR2;
else {
offsetToStringData = IntPtr.Size == 4 ? OffsetToStringData32 : OffsetToStringData64;
#pragma warning disable CS0618
Debug.Assert(offsetToStringData == RuntimeHelpers.OffsetToStringData);
#pragma warning restore CS0618
}
uint stringLength = v.StringLength;
Debug.Assert((ulong)offsetToStringData + stringLength * 2 <= size);
if (offsetToStringData > size)
return null;
return new DbgRawAddressValue(addr + offsetToStringData, stringLength * 2);
case CorElementType.Array:
case CorElementType.SZArray:
return GetArrayAddress(v);
}
return new DbgRawAddressValue(addr, size);
}
internal static DbgRawAddressValue? GetArrayAddress(CorValue v) {
var addr = v.Address;
if (addr == 0)
return null;
var arrayCount = v.ArrayCount;
if (arrayCount == 0)
return new DbgRawAddressValue(addr, 0);
var elemValue = v.GetElementAtPosition(0, out int hr);
ulong elemSize = elemValue?.Size ?? 0;
ulong elemAddr = elemValue?.Address ?? 0;
ulong totalSize = elemSize * arrayCount;
if (elemAddr == 0 || elemAddr < addr)
return null;
return new DbgRawAddressValue(elemAddr, totalSize);
}
public override DbgDotNetRawValue GetRawValue() => rawValue;
public override void Dispose() {
if (Interlocked.Exchange(ref disposed, 1) == 0)
value.Release();
}
}
}