/* 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.CodeAnalysis; using System.Linq; using dnSpy.Contracts.Debugger; using Mono.Debugger.Soft; namespace dnSpy.Debugger.DotNet.Mono.Impl { readonly struct ObjectConstantsFactory { const int FuncEvalTimeoutMilliseconds = 5000; readonly DbgProcess process; readonly ThreadMirror thread; public ObjectConstantsFactory(DbgProcess process, ThreadMirror thread) { this.process = process; this.thread = thread; } public bool TryCreate([NotNullWhen(true)] out ObjectConstants? objectConstants) { try { var offsetToStringData = GetOffsetToStringData(); if (offsetToStringData is not null) { var offsetToArrayData = GetOffsetToArrayData(); if (offsetToArrayData is not null) { objectConstants = new ObjectConstants(offsetToStringData, offsetToArrayData); return true; } } } catch { } objectConstants = null; return false; } Value? Call(MethodMirror method, IList arguments) { const InvokeOptions invokeOptions = InvokeOptions.DisableBreakpoints | InvokeOptions.SingleThreaded | InvokeOptions.Virtual; var asyncRes = method.DeclaringType.BeginInvokeMethod(thread, method, arguments, invokeOptions, null, null); if (asyncRes.AsyncWaitHandle.WaitOne(FuncEvalTimeoutMilliseconds)) return method.DeclaringType.EndInvokeMethodWithResult(asyncRes).Result; asyncRes.Abort(); return null; } int? GetOffsetToStringData() { var type = thread.Domain.Corlib.GetType("System.Runtime.CompilerServices.RuntimeHelpers"); if (type is null) return null; #pragma warning disable CS0618 var method = type.GetMethod("get_" + nameof(System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringData)); #pragma warning restore CS0618 if (method is null) return null; var res = Call(method, Array.Empty()); return (res as PrimitiveValue)?.Value as int?; } int? GetOffsetToArrayData() { var byteType = thread.Domain.Corlib.GetType("System.Byte"); var arrayType = thread.Domain.Corlib.GetType("System.Array"); var typeType = thread.Domain.Corlib.GetType("System.Type"); if (byteType is null || arrayType is null || typeType is null) return null; var createInstanceMethod = GetCreateInstance(arrayType); if (createInstanceMethod is null) return null; var args = new Value[2] { byteType.GetTypeObject(), new PrimitiveValue(thread.VirtualMachine, ElementType.I4, randomData.Length), }; var arrayMirror = Call(createInstanceMethod, args) as ArrayMirror; if (arrayMirror is null) return null; var threadTmp = thread; arrayMirror.SetValues(0, randomData.Select(a => new PrimitiveValue(threadTmp.VirtualMachine, ElementType.U1, a)).ToArray()); var arrayData = process.ReadMemory((ulong)arrayMirror.Address, randomData.Length + 0x80); var res = GetIndex(arrayData, randomData); arrayMirror.SetValues(0, randomData.Select(a => new PrimitiveValue(threadTmp.VirtualMachine, ElementType.U1, (byte)0)).ToArray()); return res; } static readonly byte[] randomData = new byte[] { 0x4A, 0xA3, 0x6F, 0x96, 0xB3, 0x8F, 0x4A, 0x5A, 0xB5, 0xD1, 0x8B, 0x76, 0x06, 0x37, 0xB6, 0x67 }; static int? GetIndex(byte[] data, byte[] pattern) { for (int i = 0; i + pattern.Length <= data.Length; i++) { bool match = true; for (int j = 0; j < pattern.Length; j++) { if (data[i + j] != pattern[j]) { match = false; break; } } if (match) return i; } return null; } MethodMirror? GetCreateInstance(TypeMirror arrayType) { foreach (var method in arrayType.GetMethods()) { if (method.Name != nameof(Array.CreateInstance)) continue; var ps = method.GetParameters(); if (ps.Length != 2) continue; if (ps[0].ParameterType.FullName != "System.Type" || ps[1].ParameterType.FullName != "System.Int32") continue; return method; } return null; } } sealed class ObjectConstants { public int? OffsetToStringData { get; } public int? OffsetToArrayData { get; } public ObjectConstants(int? offsetToStringData, int? offsetToArrayData) { OffsetToStringData = offsetToStringData; OffsetToArrayData = offsetToArrayData; } } }