From 5e3dc062af37fd94be3cd189518acdb52abad8fe Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 9 Aug 2023 09:56:55 -0300 Subject: [PATCH] [mono][debugger] Support debug inline array (#90026) * Support debug inline array for wasm * Removing extra tab * Changing behavior to use the same protocol version on wasm and on debugger-libs. * Revert last changes * Adding changes for debugger-libs support * Change where we send the information about inline array. * fix wasm compilation * Addressing @radical comments Fixing other tests. * Fix wrong change. * Adding more tests as suggested by @radical --- src/mono/mono/component/debugger-agent.c | 32 +++++- src/mono/mono/component/debugger-protocol.h | 2 +- .../BrowserDebugProxy/JObjectValueCreator.cs | 5 + .../BrowserDebugProxy/MemberReferenceResolver.cs | 9 ++ .../debugger/BrowserDebugProxy/MonoSDBHelper.cs | 2 +- .../debugger/BrowserDebugProxy/ValueTypeClass.cs | 28 +++-- .../wasm/debugger/DebuggerTestSuite/ArrayTests.cs | 95 +++++++++++++++++ .../tests/debugger-test/debugger-array-test.cs | 115 +++++++++++++++++++++ 8 files changed, 277 insertions(+), 11 deletions(-) diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index 91a2ee0..f6e62bf 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -5269,6 +5269,13 @@ buffer_add_value_full (Buffer *buf, MonoType *t, void *addr, MonoDomain *domain, continue; nfields ++; } + if (CHECK_PROTOCOL_VERSION(2, 65)) { + if (m_class_is_inlinearray (klass) && nfields == 1) + buffer_add_int (buf, m_class_inlinearray_value (klass)); + else + buffer_add_int (buf, -1); + } + buffer_add_int (buf, nfields); iter = NULL; @@ -5290,6 +5297,14 @@ buffer_add_value_full (Buffer *buf, MonoType *t, void *addr, MonoDomain *domain, } buffer_add_value_full (buf, f->type, mono_vtype_get_field_addr (addr, f), domain, FALSE, parent_vtypes, len_fixed_array != 1 ? len_fixed_array : isFixedSizeArray(f)); + if (CHECK_PROTOCOL_VERSION(2, 65)) { + if (m_class_is_inlinearray (klass) && nfields == 1) { + int element_size = mono_class_instance_size (mono_class_from_mono_type_internal (f->type)) - MONO_ABI_SIZEOF (MonoObject); + int array_size = m_class_inlinearray_value (klass); + for (int i = 1; i < array_size; i++) + buffer_add_value_full (buf, f->type, ((char*)mono_vtype_get_field_addr (addr, f)) + (i*element_size), domain, FALSE, parent_vtypes, len_fixed_array != 1 ? len_fixed_array : isFixedSizeArray(f)); + } + } } if (boxed_vtype) { @@ -5349,6 +5364,8 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void if (CHECK_PROTOCOL_VERSION(2, 61)) decode_byte (buf, &buf, limit); klass = decode_typeid (buf, &buf, limit, &d, &err); + if (CHECK_PROTOCOL_VERSION(2, 65)) + decode_int (buf, &buf, limit); //ignore inline array if (err != ERR_NONE) return err; @@ -5453,6 +5470,8 @@ decode_vtype_compute_size (MonoType *t, MonoDomain *domain, gpointer void_buf, g if (CHECK_PROTOCOL_VERSION(2, 61)) decode_byte (buf, &buf, limit); klass = decode_typeid (buf, &buf, limit, &d, &err); + if (CHECK_PROTOCOL_VERSION(2, 65)) + decode_int (buf, &buf, limit); //ignore inline array if (err != ERR_NONE) goto end; @@ -5580,6 +5599,8 @@ decode_value_compute_size (MonoType *t, int type, MonoDomain *domain, guint8 *bu decode_byte (buf, &buf, limit); if (CHECK_PROTOCOL_VERSION(2, 61)) decode_byte (buf, &buf, limit); //ignore is boxed + if (CHECK_PROTOCOL_VERSION(2, 65)) + decode_int (buf, &buf, limit); //ignore inline array decode_typeid (buf, &buf, limit, &d, &err); ret += decode_vtype_compute_size (NULL, domain, buf, &buf, limit, from_by_ref_value_type); } else { @@ -5757,6 +5778,8 @@ decode_value_internal (MonoType *t, int type, MonoDomain *domain, guint8 *addr, if (CHECK_PROTOCOL_VERSION(2, 61)) decode_byte (buf, &buf, limit); //ignore is boxed klass = decode_typeid (buf, &buf, limit, &d, &err); + if (CHECK_PROTOCOL_VERSION(2, 65)) + decode_int (buf, &buf, limit); //ignore inline array if (err != ERR_NONE) return err; @@ -9155,6 +9178,11 @@ method_commands_internal (int command, MonoMethod *method, MonoDomain *domain, g buffer_add_int (buf, header->code_size); } } + if (CHECK_PROTOCOL_VERSION (2, 65)) { + for (int i = 0; i < num_locals; ++i) { + buffer_add_int (buf, locals->locals [i].index); + } + } } mono_metadata_free_mh (header); @@ -9914,7 +9942,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) pos = - pos - 1; cmd_stack_frame_get_parameter (frame, sig, pos, buf, jit); } else { - if (!CHECK_PROTOCOL_VERSION (2, 59)) { //from newer protocol versions it's sent the pdb index + if (!CHECK_PROTOCOL_VERSION (2, 59)) { //from older protocol versions it's sent the pdb index MonoDebugLocalsInfo *locals; locals = mono_debug_lookup_locals (frame->de.method); if (locals) { @@ -9970,7 +9998,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) var = &jit->params [pos]; is_arg = TRUE; } else { - if (!CHECK_PROTOCOL_VERSION (2, 59)) { //from newer protocol versions it's sent the pdb index + if (!CHECK_PROTOCOL_VERSION (2, 59)) { //from older protocol versions it's sent the pdb index MonoDebugLocalsInfo *locals; locals = mono_debug_lookup_locals (frame->de.method); if (locals) { diff --git a/src/mono/mono/component/debugger-protocol.h b/src/mono/mono/component/debugger-protocol.h index 9a8cc5e1..9fe3c69 100644 --- a/src/mono/mono/component/debugger-protocol.h +++ b/src/mono/mono/component/debugger-protocol.h @@ -11,7 +11,7 @@ */ #define MAJOR_VERSION 2 -#define MINOR_VERSION 64 +#define MINOR_VERSION 65 typedef enum { MDBGPROT_CMD_COMPOSITE = 100 diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs index 3df1c9b..0a9f53c 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/JObjectValueCreator.cs @@ -317,6 +317,10 @@ internal sealed class JObjectValueCreator var isBoxed = retDebuggerCmdReader.ReadByte() == 1; var typeId = retDebuggerCmdReader.ReadInt32(); var className = await _sdbAgent.GetTypeName(typeId, token); + var inlineArraySize = -1; + (int MajorVersion, int MinorVersion) = await _sdbAgent.GetVMVersion(token); + if (MajorVersion == 2 && MinorVersion >= 65) + inlineArraySize = retDebuggerCmdReader.ReadInt32(); var numValues = retDebuggerCmdReader.ReadInt32(); if (className.StartsWith("System.Nullable<", StringComparison.Ordinal)) //should we call something on debugger-agent to check??? @@ -347,6 +351,7 @@ internal sealed class JObjectValueCreator typeId, isEnum, includeStatic, + inlineArraySize, token); _valueTypes[valueType.Id.Value] = valueType; return await valueType.ToJObject(_sdbAgent, forDebuggerDisplayAttribute, token); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 73bc464..5205ed5 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -395,6 +395,15 @@ namespace Microsoft.WebAssembly.Diagnostics switch (objectId.Scheme) { + case "valuetype": //can be an inlined array + { + if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType)) + throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'"); + var typeInfo = await context.SdbAgent.GetTypeInfo(valueType.TypeId, token); + if (int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx) && elementIdx >= 0 && elementIdx < valueType.InlineArray.Count) + return (JObject)valueType.InlineArray[elementIdx]["value"]; + throw new InvalidOperationException($"Index is outside the bounds of the inline array"); + } case "array": rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token); if (!elementIdxInfo.IsMultidimensional) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index f802395..b714233 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -818,7 +818,7 @@ namespace Microsoft.WebAssembly.Diagnostics private static int debuggerObjectId; private static int cmdId = 1; //cmdId == 0 is used by events which come from runtime - private const int MINOR_VERSION = 62; + private const int MINOR_VERSION = 65; private const int MAJOR_VERSION = 2; private int VmMinorVersion { get; set; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs index 0faca91..3aefe23 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ValueTypeClass.cs @@ -24,13 +24,13 @@ namespace BrowserDebugProxy private bool fieldsExpanded; private readonly string className; private JArray fields; - + public List InlineArray { get; init; } public DotnetObjectId Id { get; init; } public byte[] Buffer { get; init; } public int TypeId { get; init; } public bool IsEnum { get; init; } - public ValueTypeClass(byte[] buffer, string className, JArray fields, int typeId, bool isEnum) + public ValueTypeClass(byte[] buffer, string className, JArray fields, int typeId, bool isEnum, List inlineArray = null) { var valueTypeId = MonoSDBHelper.GetNewObjectId(); var objectId = new DotnetObjectId("valuetype", valueTypeId); @@ -42,6 +42,7 @@ namespace BrowserDebugProxy autoExpand = ShouldAutoExpand(className); Id = objectId; IsEnum = isEnum; + InlineArray = inlineArray; } public override string ToString() => $"{{ ValueTypeClass: typeId: {TypeId}, Id: {Id}, Id: {Id}, fields: {fields} }}"; @@ -54,6 +55,7 @@ namespace BrowserDebugProxy int typeId, bool isEnum, bool includeStatic, + int inlineArraySize, CancellationToken token) { var typeInfo = await sdbAgent.GetTypeInfo(typeId, token); @@ -63,6 +65,8 @@ namespace BrowserDebugProxy IReadOnlyList fieldTypes = await sdbAgent.GetTypeFields(typeId, token); JArray fields = new(); + List inlineArray = null; + JObject lastWritableFieldValue = null; if (includeStatic) { IEnumerable staticFields = @@ -77,20 +81,30 @@ namespace BrowserDebugProxy IEnumerable writableFields = fieldTypes .Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal) && !f.Attributes.HasFlag(FieldAttributes.Static)); - foreach (var field in writableFields) { - var fieldValue = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, field.Name, token, true, field.TypeId, false); - fields.Add(GetFieldWithMetadata(field, fieldValue, isStatic: false)); + lastWritableFieldValue = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, field.Name, token, isOwn: true, field.TypeId, forDebuggerDisplayAttribute: false); + fields.Add(GetFieldWithMetadata(field, lastWritableFieldValue, isStatic: false)); + } + if (inlineArraySize > 0) + { + inlineArray = new(inlineArraySize+1); + inlineArray.Add(lastWritableFieldValue); + var firstFieldtypeId = writableFields.First().TypeId; + for (int i = 1; i < inlineArraySize; i++) + { + //the valuetype has a single instance field in inline-arrays + var inlineArrayItem = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, $"{i}", token, isOwn: true, firstFieldtypeId, forDebuggerDisplayAttribute: false); + inlineArray.Add(inlineArrayItem); + } } - long endPos = cmdReader.BaseStream.Position; cmdReader.BaseStream.Position = initialPos; byte[] valueTypeBuffer = new byte[endPos - initialPos]; cmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); cmdReader.BaseStream.Position = endPos; - return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, isEnum); + return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, isEnum, inlineArray); JObject GetFieldWithMetadata(FieldTypeClass field, JObject fieldValue, bool isStatic) { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs index ff0712e..f94be60 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs @@ -670,5 +670,100 @@ namespace DebuggerTests CheckNumber(int_arr_3, "1, 2, 1", 121); CheckNumber(int_arr_3, "1, 2, 2", 122); } + + [ConditionalTheory(nameof(RunningOnChrome))] + [InlineData("s")] + [InlineData("classWithInlineArrayField.myInlineArray")] + [InlineData("classWithInlineArrayField.InlineArrayProp")] + [InlineData("classWithInlineArrayField.myStructWithInlineArray.myInlineArray")] + public async Task InspectInlineArray(string varName) + { + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'[debugger-test] DebuggerTests.InlineArray:run'" + + "); }, 1);"; + + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 441, 12, "DebuggerTests.InlineArray.run"); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id, + ($"{varName}[0]", TObject("DebuggerTests.InlineArray.E")), + ($"{varName}[a]", TObject("DebuggerTests.InlineArray.E")), + ($"{varName}[b]", TObject("DebuggerTests.InlineArray.E")), + ($"{varName}[1]", TObject("DebuggerTests.InlineArray.E"))); + var (_, res) = await EvaluateOnCallFrame(id,$"{varName}[43]", expect_ok: false); + Assert.Equal($"Unable to evaluate element access '{varName}[43]': Index is outside the bounds of the inline array", res.Error["result"]?["description"]?.Value()); + + var (s_zero, _) = await EvaluateOnCallFrame(id, $"{varName}[0]"); + await CheckValue(s_zero, TObject("DebuggerTests.InlineArray.E"), nameof(s_zero)); + var s_zero_props = await GetProperties(s_zero["objectId"]?.Value()); + await CheckProps(s_zero_props, new + { + x = TNumber(1), + y = TNumber(2), + o = TObject("DebuggerTests.InlineArray.One") + }, "s_zero_props#1"); + + var (s_one, _) = await EvaluateOnCallFrame(id, $"{varName}[1]"); + await CheckValue(s_one, TObject("DebuggerTests.InlineArray.E"), nameof(s_one)); + var ss_one_props = await GetProperties(s_one["objectId"]?.Value()); + await CheckProps(ss_one_props, new + { + x = TNumber(3), + y = TNumber(4), + o = TObject("DebuggerTests.InlineArray.Two") + }, "s_one_props#1"); + } + [Fact] + public async Task InspectInlineArray2() + { + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'[debugger-test] DebuggerTests.InlineArray:run2'" + + "); }, 1);"; + + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, 459, 12, "DebuggerTests.InlineArray.run2"); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); + await EvaluateOnCallFrameAndCheck(id, + ($"a2[0]", TNumber(1)), + ($"a3[0]", TNumber(2)), + ($"a4[0]", TObject("DebuggerTests.InlineArray.E")), + ($"Arr4.myStaticField", TNumber(50)) + ); + + var (_, res) = await EvaluateOnCallFrame(id,$"a3[1]", expect_ok: false); + Assert.Equal($"Unable to evaluate element access 'a3[1]': Index is outside the bounds of the inline array", res.Error["result"]?["description"]?.Value()); + + var (s_zero, _) = await EvaluateOnCallFrame(id, $"a4[0]"); + await CheckValue(s_zero, TObject("DebuggerTests.InlineArray.E"), nameof(s_zero)); + var s_zero_props = await GetProperties(s_zero["objectId"]?.Value()); + await CheckProps(s_zero_props, new + { + x = TNumber(1), + y = TNumber(2), + o = TObject("DebuggerTests.InlineArray.One") + }, "s_zero_props#1"); + + var (s_one, _) = await EvaluateOnCallFrame(id, $"a4[1]"); + await CheckValue(s_one, TObject("DebuggerTests.InlineArray.E"), nameof(s_one)); + var ss_one_props = await GetProperties(s_one["objectId"]?.Value()); + await CheckProps(ss_one_props, new + { + x = TNumber(3), + y = TNumber(4), + o = TObject("DebuggerTests.InlineArray.Two") + }, "s_one_props#1"); + + var (s_two, _) = await EvaluateOnCallFrame(id, $"a4"); + await CheckValue(s_two, TObject("DebuggerTests.InlineArray.Arr4"), nameof(s_two)); + var s_two_props = await GetProperties(s_two["objectId"]?.Value()); + await CheckProps(s_two_props, new + { + myStaticField = TNumber(50), + e = TObject("DebuggerTests.InlineArray.E"), + Length = TNumber(42) + }, "s_two_props#1"); + } } } diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs index f1a2b41..c05329b 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-array-test.cs @@ -345,4 +345,119 @@ namespace DebuggerTests Console.WriteLine($"int_arr: {int_arr_3.Length}"); } } + + public class InlineArray + { + struct StructWithInlineArray + { + public Arr1 myInlineArray; + } + class ClassWithInlineArrayField + { + public Arr1 myInlineArray; + public Arr1 InlineArrayProp => myInlineArray; + public StructWithInlineArray myStructWithInlineArray; + } + class One {} + class Two {} + class Three {} + class Four {} + struct E + { + public int x; + public int y; + public object o; + } + + [System.Runtime.CompilerServices.InlineArray(Length)] + struct Arr1 + { + public const int Length = 42; + public E e; + } + + [System.Runtime.CompilerServices.InlineArray(Length)] + struct Arr2 + { + public const int Length = 42; + public int e; + } + + [System.Runtime.CompilerServices.InlineArray(1)] + struct Arr3 + { + public int e; + } + + [System.Runtime.CompilerServices.InlineArray(Length)] + struct Arr4 + { + public static int myStaticField = 50; + public const int Length = 42; + public E e; + } + + private static Arr1 Initialize(Arr1 s) + { + s[0].o = new One(); + s[0].x = 1; + s[0].y = 2; + s[1].o = new Two(); + s[1].x = 3; + s[1].y = 4; + s[2].o = new Three(); + s[2].x = 5; + s[2].y = 6; + s[3].o = new Four(); + s[3].x = 7; + s[3].y = 8; + return s; + } + public static void run() + { + int a = 0; + int b = 1; + Arr1 s = default; + s = Initialize(s); + ClassWithInlineArrayField classWithInlineArrayField = new (); + s = Initialize(classWithInlineArrayField.myInlineArray); + classWithInlineArrayField.myInlineArray[0].o = new One(); + classWithInlineArrayField.myInlineArray[0].x = 1; + classWithInlineArrayField.myInlineArray[0].y = 2; + classWithInlineArrayField.myInlineArray[1].o = new Two(); + classWithInlineArrayField.myInlineArray[1].x = 3; + classWithInlineArrayField.myInlineArray[1].y = 4; + //classWithInlineArrayField.InlineArrayProp[0].o = new One(); + //classWithInlineArrayField.InlineArrayProp[0].x = 1; + //classWithInlineArrayField.InlineArrayProp[0].y = 2; + //classWithInlineArrayField.InlineArrayProp[1].o = new Two(); + //classWithInlineArrayField.InlineArrayProp[1].x = 3; + //classWithInlineArrayField.InlineArrayProp[1].y = 4; + classWithInlineArrayField.myStructWithInlineArray.myInlineArray[0].o = new One(); + classWithInlineArrayField.myStructWithInlineArray.myInlineArray[0].x = 1; + classWithInlineArrayField.myStructWithInlineArray.myInlineArray[0].y = 2; + classWithInlineArrayField.myStructWithInlineArray.myInlineArray[1].o = new Two(); + classWithInlineArrayField.myStructWithInlineArray.myInlineArray[1].x = 3; + classWithInlineArrayField.myStructWithInlineArray.myInlineArray[1].y = 4; + System.Diagnostics.Debugger.Break(); + Console.WriteLine(s[0].o.GetType().Name); + } + + public static void run2() + { + Arr2 a2 = default; //test with primitive type + Arr3 a3 = default; //test with length==1 + Arr4 a4 = default; //test with static field + a2[0] = 1; + a3[0] = 2; + a4[0].o = new One(); + a4[0].x = 1; + a4[0].y = 2; + a4[1].o = new Two(); + a4[1].x = 3; + a4[1].y = 4; + Console.WriteLine($"olha thays - {Arr4.myStaticField}"); + System.Diagnostics.Debugger.Break(); + } + } } -- 2.7.4