MonoDebugHandle *handle = mono_debug_get_handle (assembly_image);
if (handle) {
MonoPPDBFile *ppdb = handle->ppdb;
- if (!mono_ppdb_is_embedded (ppdb)) { //if it's an embedded pdb we don't need to send pdb extrated to DebuggerProxy.
+ if (ppdb && !mono_ppdb_is_embedded (ppdb)) { //if it's an embedded pdb we don't need to send pdb extrated to DebuggerProxy.
pdb_image = mono_ppdb_get_image (ppdb);
mono_wasm_asm_loaded (assembly_image->assembly_name, assembly_image->raw_data, assembly_image->raw_data_len, pdb_image->raw_data, pdb_image->raw_data_len);
return;
case MONO_TYPE_OBJECT: {
MonoObject *obj = *(MonoObject**)addr;
+ if (!obj) {
+ mono_wasm_add_obj_var ("object", NULL, 0);
+ break;
+ }
MonoClass *klass = obj->vtable->klass;
if (!klass) {
// boxed null
case MONO_TYPE_ARRAY:
case MONO_TYPE_CLASS: {
MonoObject *obj = *(MonoObject**)addr;
+ if (!obj) {
+ char *class_name = mono_type_full_name (type);
+ mono_wasm_add_func_var (class_name, NULL, 0);
+ g_free (class_name);
+ return TRUE;
+ }
MonoClass *klass = type->data.klass;
if (m_class_is_valuetype (mono_object_class (obj))) {
addr = mini_get_interp_callbacks ()->frame_get_local (frame, pos);
}
- PRINT_DEBUG_MSG (2, "adding val %p type [%p] %s\n", addr, type, mono_type_full_name (type));
+ PRINT_DEBUG_MSG (2, "adding val %p type 0x%x %s\n", addr, type->type, mono_type_full_name (type));
return describe_value(type, addr, gpflags);
}
(See https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=xunit for filter options)
-Additional arguments for `dotnet test` can be passed via `TEST_ARGS`. Though only one of `TEST_ARGS`, or `TEST_FILTER` can be used at a time.
+Additional arguments for `dotnet test` can be passed via `MSBUILD_ARGS` or `TEST_ARGS`. For example `MSBUILD_ARGS="/p:WasmDebugLevel=5"`. Though only one of `TEST_ARGS`, or `TEST_FILTER` can be used at a time.
## Run samples
<MSBuild Projects="@(WasmAppBuildProject)"
Properties="Configuration=Debug"
- Targets="Build;Publish"/>
+ Targets="Build"/>
</Target>
<Target Name="CopyAppZipToHelixTestDir"
<Error Condition="'@(_WasmAssembliesInternal)' == ''" Text="Item _WasmAssembliesInternal is empty" />
<Error Condition="'$(_IsEMSDKMissing)' == 'true'"
Text="$(_EMSDKMissingErrorMessage) Emscripten SDK is required for AOT'ing assemblies." />
+ <Error Condition="!Exists('$(MonoAotCrossCompilerPath)')" Text="MonoAotCrossCompilerPath=$(MonoAotCrossCompilerPath) doesn't exist" />
<ItemGroup>
<MonoAOTCompilerDefaultAotArguments Include="no-opt" />
<WasmAppDir>$([MSBuild]::NormalizeDirectory($(WasmAppDir)))</WasmAppDir>
<MicrosoftNetCoreAppRuntimePackRidDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir)))</MicrosoftNetCoreAppRuntimePackRidDir>
<MicrosoftNetCoreAppRuntimePackRidNativeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir), 'native'))</MicrosoftNetCoreAppRuntimePackRidNativeDir>
- <MonoAotCrossCompilerPath Condition="'$(MonoAotCrossCompilerPath)' == ''">$([MSBuild]::NormalizePath($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'cross', $(PackageRID), 'mono-aot-cross$(_ExeExt)'))</MonoAotCrossCompilerPath>
+ <MonoAotCrossCompilerPath Condition="'$(MonoAotCrossCompilerPath)' == ''">$([MSBuild]::NormalizePath($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'cross', $(RuntimeIdentifier), 'mono-aot-cross$(_ExeExt)'))</MonoAotCrossCompilerPath>
<!-- emcc, and mono-aot-cross don't like relative paths for output files -->
<_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm'))</_WasmIntermediateOutputPath>
</PropertyGroup>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace DebuggerTests
+{
+ public class AssignmentTests : DebuggerTestBase
+ {
+ public static TheoryData<string, JObject, JObject> GetTestData => new TheoryData<string, JObject, JObject>
+ {
+ { "MONO_TYPE_OBJECT", TObject("object", is_null: true), TObject("object") },
+ { "MONO_TYPE_CLASS", TObject("DebuggerTests.MONO_TYPE_CLASS", is_null: true), TObject("DebuggerTests.MONO_TYPE_CLASS") },
+ { "MONO_TYPE_BOOLEAN", TBool(default), TBool(true) },
+ { "MONO_TYPE_CHAR", TSymbol("0 '\u0000'"), TSymbol("97 'a'") },
+ { "MONO_TYPE_STRING", TString(default), TString("hello") },
+ { "MONO_TYPE_ENUM", TEnum("DebuggerTests.RGB", "Red"), TEnum("DebuggerTests.RGB", "Blue") },
+ { "MONO_TYPE_ARRAY", TObject("byte[]", is_null: true), TArray("byte[]", 2) },
+ { "MONO_TYPE_VALUETYPE", TValueType("DebuggerTests.Point"), TValueType("DebuggerTests.Point") },
+ { "MONO_TYPE_VALUETYPE2", TValueType("System.Decimal","0"), TValueType("System.Decimal", "1.1") },
+ { "MONO_TYPE_GENERICINST", TObject("System.Func<int>", is_null: true), TDelegate("System.Func<int>", "int Prepare ()") },
+ { "MONO_TYPE_FNPTR", TPointer("*()", is_null: true), TPointer("*()") },
+ { "MONO_TYPE_PTR", TPointer("int*", is_null: true), TPointer("int*") },
+ { "MONO_TYPE_I1", TNumber(0), TNumber(-1) },
+ { "MONO_TYPE_I2", TNumber(0), TNumber(-1) },
+ { "MONO_TYPE_I4", TNumber(0), TNumber(-1) },
+ { "MONO_TYPE_I8", TNumber(0), TNumber(-1) },
+ { "MONO_TYPE_U1", TNumber(0), TNumber(1) },
+ { "MONO_TYPE_U2", TNumber(0), TNumber(1) },
+ { "MONO_TYPE_U4", TNumber(0), TNumber(1) },
+ { "MONO_TYPE_U8", TNumber(0), TNumber(1) },
+ { "MONO_TYPE_R4", TNumber(0), TNumber("3.1414999961853027") },
+ { "MONO_TYPE_R8", TNumber(0), TNumber("3.1415") },
+ };
+
+ [Theory]
+ [MemberData("GetTestData")]
+ async Task InspectVariableBeforeAndAfterAssignment(string clazz, JObject checkDefault, JObject checkValue)
+ {
+ await SetBreakpointInMethod("debugger-test", "DebuggerTests." + clazz, "Prepare", 2);
+ await EvaluateAndCheck("window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests." + clazz + ":Prepare'); })", null, -1, -1, "Prepare");
+
+ // 1) check un-assigned variables
+ await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-assignment-test.cs", -1, -1, "TestedMethod",
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(2, locals.Count());
+ Check(locals, "r", checkDefault);
+ }
+ );
+
+ // 2) check assigned variables
+ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-assignment-test.cs", -1, -1, "TestedMethod", times: 3,
+ locals_fn: (locals) =>
+ {
+ Assert.Equal(2, locals.Count());
+ Check(locals, "r", checkValue);
+ }
+ );
+ }
+ }
+}
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"/usr/bin/chromium",
+ "C:/Program Files/Google/Chrome/Application/chrome.exe",
"/usr/bin/chromium-browser",
};
static string chrome_path;
public virtual async Task InitializeAsync()
{
- Func<InspectorClient, CancellationToken, List<(string, Task<Result>)>> fn = (client, token) =>
- {
- Func<string, (string, Task<Result>)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token));
- var init_cmds = new List<(string, Task<Result>)>
- {
+ Func<InspectorClient, CancellationToken, List<(string, Task<Result>)>> fn = (client, token) =>
+ {
+ Func<string, (string, Task<Result>)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token));
+ var init_cmds = new List<(string, Task<Result>)>
+ {
getInitCmdFn("Profiler.enable"),
getInitCmdFn("Runtime.enable"),
getInitCmdFn("Debugger.enable"),
getInitCmdFn("Runtime.runIfWaitingForDebugger")
- };
+ };
- return init_cmds;
- };
+ return init_cmds;
+ };
await Ready();
await insp.OpenSessionAsync(fn);
function_name,
wait_for_event_fn: async (pause_location) =>
{
- //make sure we're on the right bp
+ //make sure we're on the right bp
- Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
+ Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value<string>());
var top_frame = pause_location!["callFrames"]?[0];
return l;
}
- internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false)
+ internal JToken Check(JToken locals, string name, JObject expected)
+ {
+ var l = GetAndAssertObjectWithName(locals, name);
+ CheckValue(l["value"], expected, name).Wait();
+ return l;
+ }
+
+ internal JToken CheckObject(JToken locals, string name, string class_name, string subtype = null, bool is_null = false, string description = null)
{
var l = GetAndAssertObjectWithName(locals, name);
var val = l["value"];
- CheckValue(val, TObject(class_name, is_null: is_null), name).Wait();
+ CheckValue(val, TObject(class_name, is_null: is_null, description: description), name).Wait();
Assert.True(val["isValueType"] == null || !val["isValueType"].Value<bool>());
return l;
Assert.Equal(value, val);
}
- internal JToken CheckValueType(JToken locals, string name, string class_name)
+ internal JToken CheckValueType(JToken locals, string name, string class_name, string description=null)
{
var l = GetAndAssertObjectWithName(locals, name);
- CheckValue(l["value"], TValueType(class_name), name).Wait();
+ CheckValue(l["value"], TValueType(class_name, description: description), name).Wait();
return l;
}
{
functionDeclaration = fn,
objectId = obj["value"]?["objectId"]?.Value<string>(),
- arguments = new[] { new { value = property } , new { value = newvalue } },
+ arguments = new[] { new { value = property }, new { value = newvalue } },
silent = true
});
var res = await cli.SendCommand("Runtime.callFunctionOn", req, token);
if (locals_fn != null)
{
var locals = await GetProperties(wait_res["callFrames"][0]["callFrameId"].Value<string>());
- locals_fn(locals);
+ try
+ {
+ locals_fn(locals);
+ }
+ catch (System.AggregateException ex)
+ {
+ throw new AggregateException(ex.Message + " \n" + locals.ToString(), ex);
+ }
}
return wait_res;
AssertEqual(exp_val_str, actual_field_val_str, $"[{label}] Value for json property named {jp.Name} didn't match.");
}
}
- catch
+ catch (Exception ex)
{
- Console.WriteLine($"Expected: {exp_val}. Actual: {actual_val}");
+ Console.WriteLine($"{ex.Message} \nExpected: {exp_val} \nActual: {actual_val}");
throw;
}
}
internal async Task<Result> SetBreakpoint(string url_key, int line, int column, bool expect_ok = true, bool use_regex = false, string condition = "")
{
var bp1_req = !use_regex ?
- JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], condition}) :
- JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, condition});
+ JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], condition }) :
+ JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, condition });
var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token);
Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr);
internal static JObject TNumber(uint value) =>
JObject.FromObject(new { type = "number", value = @value.ToString(), description = value.ToString() });
+ internal static JObject TNumber(string value) =>
+ JObject.FromObject(new { type = "number", value = @value.ToString(), description = value });
+
internal static JObject TValueType(string className, string description = null, object members = null) =>
JObject.FromObject(new { type = "object", isValueType = true, className = className, description = description ?? className });
async Task OnMessage(string method, JObject args, CancellationToken token)
{
+ bool fail = false;
switch (method)
{
case "Debugger.paused":
case "Runtime.consoleAPICalled":
_logger.LogInformation(FormatConsoleAPICalled(args));
break;
+ case "Inspector.detached":
+ case "Inspector.targetCrashed":
+ case "Inspector.targetReloadedAfterCrash":
+ fail = true;
+ break;
+ case "Runtime.exceptionThrown":
+ _logger.LogDebug($"Failing all waiters because: {method}: {args}");
+ fail = true;
+ break;
}
if (eventListeners.TryGetValue(method, out var listener))
{
await listener(args, token).ConfigureAwait(false);
}
- else if (String.Compare(method, "Runtime.exceptionThrown") == 0)
+ else if (fail)
{
- _logger.LogDebug($"Failing all waiters because: {method}: {args}");
FailAllWaiters(new ArgumentException(args.ToString()));
}
}
<TargetFramework>$(AspNetCoreAppCurrent)</TargetFramework>
<OutputType>Library</OutputType>
<Configuration>Debug</Configuration>
- <RuntimeConfiguration>Release</RuntimeConfiguration>
+ <RuntimeConfiguration Condition="'$(RuntimeConfiguration)'==''">Release</RuntimeConfiguration>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>219</NoWarn>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+namespace DebuggerTests
+{
+ public class StepInTest<T>
+ {
+ public static int TestedMethod(T value)
+ {
+ // 1) break here and check un-assigned variables
+ T r;
+ r = value;
+ // 2) break here and check assigned variables
+ return 0;
+ }
+ }
+
+ public class MONO_TYPE_OBJECT
+ {
+ public static int Prepare()
+ {
+ var value = new object();
+ return StepInTest<object>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_CLASS
+ {
+ public static int Prepare()
+ {
+ var value = new MONO_TYPE_CLASS();
+ return StepInTest<MONO_TYPE_CLASS>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_BOOLEAN
+ {
+ public static int Prepare()
+ {
+ var value = true;
+ return StepInTest<bool>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_CHAR
+ {
+ public static int Prepare()
+ {
+ var value = 'a';
+ return StepInTest<char>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_I1
+ {
+ public static int Prepare()
+ {
+ sbyte value = -1;
+ return StepInTest<sbyte>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_I2
+ {
+ public static int Prepare()
+ {
+ short value = -1;
+ return StepInTest<short>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_I4
+ {
+ public static int Prepare()
+ {
+ int value = -1;
+ return StepInTest<int>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_I8
+ {
+ public static int Prepare()
+ {
+ long value = -1;
+ return StepInTest<long>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_U1
+ {
+ public static int Prepare()
+ {
+ byte value = 1;
+ return StepInTest<byte>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_U2
+ {
+ public static int Prepare()
+ {
+ ushort value = 1;
+ return StepInTest<ushort>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_U4
+ {
+ public static int Prepare()
+ {
+ uint value = 1;
+ return StepInTest<uint>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_U8
+ {
+ public static int Prepare()
+ {
+ ulong value = 1;
+ return StepInTest<ulong>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_R4
+ {
+ public static int Prepare()
+ {
+ float value = 3.1415F;
+ return StepInTest<float>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_R8
+ {
+ public static int Prepare()
+ {
+ double value = 3.1415D;
+ return StepInTest<double>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_STRING
+ {
+ public static int Prepare()
+ {
+ string value = "hello";
+ return StepInTest<string>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_ENUM
+ {
+ public static int Prepare()
+ {
+ RGB value = RGB.Blue;
+ return StepInTest<RGB>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_ARRAY
+ {
+ public static int Prepare()
+ {
+ byte[] value = new byte[2] { 1, 2 };
+ return StepInTest<byte[]>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_VALUETYPE
+ {
+ public static int Prepare()
+ {
+ Point value = new Point();
+ return StepInTest<Point>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_VALUETYPE2
+ {
+ public static int Prepare()
+ {
+ Decimal value = 1.1m;
+ return StepInTest<Decimal>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_GENERICINST
+ {
+ public static int Prepare()
+ {
+ Func<int> value = MONO_TYPE_GENERICINST.Prepare;
+ return StepInTest<Func<int>>.TestedMethod(value);
+ }
+ }
+
+ public class MONO_TYPE_FNPTR
+ {
+ public unsafe static int Prepare()
+ {
+ delegate*<int> value = &MONO_TYPE_FNPTR.Prepare;
+ return TestedMethod(value);
+ }
+
+ public unsafe static int TestedMethod(delegate*<int> value)
+ {
+ delegate*<int> r;
+ r = value;
+ return 0;
+ }
+ }
+
+ public class MONO_TYPE_PTR
+ {
+ public unsafe static int Prepare()
+ {
+ int a = 1; int* value = &a;
+ return TestedMethod(value);
+ }
+
+ public unsafe static int TestedMethod(int* value)
+ {
+ int* r;
+ r = value;
+ return 0;
+ }
+ }
+}
<PropertyGroup>
<WasmAppDir>$(AppDir)</WasmAppDir>
<WasmMainJSPath>$(MonoProjectRoot)wasm\runtime-test.js</WasmMainJSPath>
- <WasmDebugLevel>1</WasmDebugLevel>
+ <WasmDebugLevel Condition="'$(WasmDebugLevel)'==''">1</WasmDebugLevel>
<WasmResolveAssembliesBeforeBuild>true</WasmResolveAssembliesBeforeBuild>
</PropertyGroup>