Skip to content
Merged
8 changes: 7 additions & 1 deletion src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ internal class MethodInfo
public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0;
public int IsAsync { get; set; }
public bool IsHiddenFromDebugger { get; }
public bool HasStepThroughAttribute { get; }
public TypeInfo TypeInfo { get; }

public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader)
Expand Down Expand Up @@ -379,7 +380,12 @@ public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle,
var name = asmMetadataReader.GetString(asmMetadataReader.GetTypeReference((TypeReferenceHandle)container).Name);
if (name == "DebuggerHiddenAttribute")
{
this.IsHiddenFromDebugger = true;
IsHiddenFromDebugger = true;
break;
}
if (name == "DebuggerStepThroughAttribute")
{
HasStepThroughAttribute = true;
break;
}

Expand Down
2 changes: 2 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData)
public TaskCompletionSource<DebugStore> ready;
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
public bool IsSkippingHiddenMethod { get; set; }
public bool IsSteppingThroughMethod { get; set; }
public bool IsResumedAfterBp { get; set; }
public int ThreadId { get; set; }
public int Id { get; set; }
public object AuxData { get; set; }
Expand Down
83 changes: 69 additions & 14 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal class MonoProxy : DevToolsProxy
private const string sPauseOnCaught = "pause_on_caught";
// index of the runtime in a same JS page/process
public int RuntimeId { get; private init; }
public bool JustMyCode { get; private set; }

public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList, int runtimeId = 0) : base(loggerFactory)
{
Expand Down Expand Up @@ -573,10 +574,33 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
return true;
}
}
case "DotnetDebugger.justMyCode":
{
try
{
SetJustMyCode(id, args, token);
}
catch (Exception)
{
SendResponse(id,
Result.Exception(new ArgumentException(
$"DotnetDebugger.justMyCode got incorrect argument ({args})")),
token);
}
return true;
}
}

return false;
}
private void SetJustMyCode(MessageId id, JObject args, CancellationToken token)
{
var isEnabled = args["enabled"]?.Value<bool>();
if (isEnabled == null)
throw new ArgumentException();
JustMyCode = isEnabled.Value;
SendResponse(id, Result.OkFromObject(new { justMyCodeEnabled = JustMyCode }), token);
}
private async Task<bool> CallOnFunction(MessageId id, JObject args, CancellationToken token)
{
var context = GetContext(id);
Expand Down Expand Up @@ -813,24 +837,44 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
DebugStore store = await LoadStore(sessionId, token);
var method = await context.SdbAgent.GetMethodInfo(methodId, token);

if (context.IsSkippingHiddenMethod == true)
var shouldReturn = await SkipMethod(
isSkippable: context.IsSkippingHiddenMethod,
shouldBeSkipped: event_kind != EventKind.UserBreak,
StepKind.Over);
context.IsSkippingHiddenMethod = false;
if (shouldReturn)
return true;

shouldReturn = await SkipMethod(
isSkippable: context.IsSteppingThroughMethod,
shouldBeSkipped: event_kind != EventKind.UserBreak && event_kind != EventKind.Breakpoint,
StepKind.Over);
context.IsSteppingThroughMethod = false;
if (shouldReturn)
return true;

if (j == 0 && (method?.Info.HasStepThroughAttribute == true || method?.Info.IsHiddenFromDebugger == true))
{
context.IsSkippingHiddenMethod = false;
if (event_kind != EventKind.UserBreak)
if (method.Info.IsHiddenFromDebugger)
{
await context.SdbAgent.Step(context.ThreadId, StepKind.Over, token);
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
return true;
if (event_kind == EventKind.Step)
context.IsSkippingHiddenMethod = true;
if (await SkipMethod(isSkippable: true, shouldBeSkipped: true, StepKind.Out))
return true;
}
}

if (j == 0 && method?.Info.IsHiddenFromDebugger == true)
{
if (event_kind == EventKind.Step)
context.IsSkippingHiddenMethod = true;
await context.SdbAgent.Step(context.ThreadId, StepKind.Out, token);
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
return true;
if (event_kind == EventKind.Step ||
(JustMyCode && (event_kind == EventKind.Breakpoint || event_kind == EventKind.UserBreak)))
{
if (context.IsResumedAfterBp)
context.IsResumedAfterBp = false;
else if (event_kind != EventKind.UserBreak)
context.IsSteppingThroughMethod = true;
if (await SkipMethod(isSkippable: true, shouldBeSkipped: true, StepKind.Out))
return true;
}
if (event_kind == EventKind.Breakpoint)
context.IsResumedAfterBp = true;
}

SourceLocation location = method?.Info.GetLocationByIl(il_pos);
Expand Down Expand Up @@ -910,6 +954,17 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
SendEvent(sessionId, "Debugger.paused", o, token);

return true;

async Task<bool> SkipMethod(bool isSkippable, bool shouldBeSkipped, StepKind stepKind)
{
if (isSkippable && shouldBeSkipped)
{
await context.SdbAgent.Step(context.ThreadId, stepKind, token);
await SendCommand(sessionId, "Debugger.resume", new JObject(), token);
return true;
}
return false;
}
}
private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, CancellationToken token)
{
Expand Down
131 changes: 127 additions & 4 deletions src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ await EvaluateAndCheck(


[Fact]
public async Task DebuggerAttributeNoStopInDebuggerHidden()
public async Task DebuggerHiddenNoStopOnBp()
{
var bp_hidden = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "HiddenMethod", 1);
var bp_visible = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "VisibleMethod", 1);
Expand All @@ -683,7 +683,7 @@ await EvaluateAndCheck(
}

[Fact]
public async Task DebuggerAttributeStopOnDebuggerHiddenCallWithDebuggerBreakCall()
public async Task DebuggerHiddenStopOnUserBp()
{
var bp_init = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunDebuggerBreak", 0);
var init_location = await EvaluateAndCheck(
Expand All @@ -708,6 +708,131 @@ await SendCommandAndCheck(null, "Debugger.resume",
"VisibleMethodDebuggerBreak");
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task StepThroughAttributeStepInNoBp(bool justMyCodeEnabled)
{
var bp_init = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunStepThrough", 1);
if (justMyCodeEnabled)
await SetJustMyCode(true);

var init_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerAttribute:RunStepThrough'); }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs",
bp_init.Value["locations"][0]["lineNumber"].Value<int>(),
bp_init.Value["locations"][0]["columnNumber"].Value<int>(),
"RunStepThrough"
);
await SendCommandAndCheck(null, "Debugger.stepInto", "dotnet://debugger-test.dll/debugger-test.cs", 868, 8, "RunStepThrough");
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task StepThroughAttributeStepInWithBp(bool justMyCodeEnabled)
{
var bp_init = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunStepThrough", 1);
var bp1_decorated_fun = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "NotStopOnJustMyCode", 1);
var bp2_decorated_fun = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "NotStopOnJustMyCode", 3);

var init_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerAttribute:RunStepThrough'); }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs",
bp_init.Value["locations"][0]["lineNumber"].Value<int>(),
bp_init.Value["locations"][0]["columnNumber"].Value<int>(),
"RunStepThrough"
);

if (justMyCodeEnabled)
{
await SetJustMyCode(true);
await SendCommandAndCheck(null, "Debugger.stepInto", "dotnet://debugger-test.dll/debugger-test.cs", 868, 8, "RunStepThrough");
}
else
{
var line1 = bp1_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>();
var line2 = bp2_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>();
var line3 = 867;
var step_throgh_fun = "NotStopOnJustMyCode";
var outer_fun = "RunStepThrough";
await SendCommandAndCheck(null, "Debugger.stepInto", "dotnet://debugger-test.dll/debugger-test.cs", line1, 8, step_throgh_fun);
await SendCommandAndCheck(null, "Debugger.stepInto", "dotnet://debugger-test.dll/debugger-test.cs", line2, 8, step_throgh_fun);
await SendCommandAndCheck(null, "Debugger.stepInto", "dotnet://debugger-test.dll/debugger-test.cs", line3, 8, outer_fun);
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task StepThroughAttributeResumeWithBp(bool justMyCodeEnabled)
{
var bp_init = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunStepThrough", 1);
var bp1_decorated_fun = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "NotStopOnJustMyCode", 1);
var bp_outside_decorated_fun = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunStepThrough", 2);

var init_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerAttribute:RunStepThrough'); }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs",
bp_init.Value["locations"][0]["lineNumber"].Value<int>(),
bp_init.Value["locations"][0]["columnNumber"].Value<int>(),
"RunStepThrough"
);

if (justMyCodeEnabled)
await SetJustMyCode(true);
else
{
var line1 = bp1_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>();
var function_name1 = "NotStopOnJustMyCode";
await SendCommandAndCheck(null, "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", line1, 8, function_name1);
}

var line2 = bp_outside_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>();
var function_name2 = "RunStepThrough";
await SendCommandAndCheck(null, "Debugger.resume", "dotnet://debugger-test.dll/debugger-test.cs", line2, 8, function_name2);
}

[Theory]
[InlineData(false, "Debugger.resume")]
[InlineData(false, "Debugger.stepInto")]
[InlineData(true, "Debugger.stepInto")]
[InlineData(true, "Debugger.resume")]
public async Task StepThroughAttributeWithUserBp(bool justMyCodeEnabled, string debuggingFunction)
{
var bp_init = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunStepThrough", 2);
var bp_outside_decorated_fun = await SetBreakpointInMethod("debugger-test.dll", "DebuggerAttribute", "RunStepThrough", 3);

var init_location = await EvaluateAndCheck(
"window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerAttribute:RunStepThrough'); }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs",
bp_init.Value["locations"][0]["lineNumber"].Value<int>(),
bp_init.Value["locations"][0]["columnNumber"].Value<int>(),
"RunStepThrough"
);

int line1, line2;
string function_name1, function_name2;

if (justMyCodeEnabled)
{
await SetJustMyCode(true);
line1 = bp_outside_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>() - 1;
function_name1 = "RunStepThrough";
line2 = bp_outside_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>();
function_name2 = "RunStepThrough";
}
else
{
line1 = 862;
function_name1 = "NotStopOnJustMyCodeUserBp";
line2 = bp_outside_decorated_fun.Value["locations"][0]["lineNumber"].Value<int>();
function_name2 = "RunStepThrough";
}
await SendCommandAndCheck(null, debuggingFunction, "dotnet://debugger-test.dll/debugger-test.cs", line1, 8, function_name1);
await SendCommandAndCheck(null, debuggingFunction, "dotnet://debugger-test.dll/debugger-test.cs", line2, 4, function_name2);
}

[Fact]
public async Task CreateGoodBreakpointAndHitGoToWasmPageWithoutAssetsComeBackAndHitAgain()
{
Expand Down Expand Up @@ -776,7 +901,5 @@ await EvaluateAndCheck(
}
);
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,14 @@ internal async Task<JObject> LoadAssemblyAndTestHotReload(string asm_file, strin
await cli.SendCommand("Runtime.evaluate", run_method, token);
return await insp.WaitFor(Inspector.PAUSE);
}

internal async Task SetJustMyCode(bool enabled)
{
var req = JObject.FromObject(new { enabled = enabled });
var res = await cli.SendCommand("DotnetDebugger.justMyCode", req, token);
Assert.True(res.IsOk);
Assert.Equal(res.Value["justMyCodeEnabled"], enabled);
}
}

class DotnetObjectId
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ public async Task InspectLocalsUsingClassFromLibraryUsingDebugTypeFull()

await EvaluateAndCheck(
"window.setTimeout(function() {" + expression + "; }, 1);",
"dotnet://debugger-test.dll/debugger-test.cs", 860, 8,
"dotnet://debugger-test.dll/debugger-test.cs", 880, 8,
"CallToEvaluateLocal",
wait_for_event_fn: async (pause_location) =>
{
Expand Down
20 changes: 20 additions & 0 deletions src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,26 @@ public static void RunDebuggerBreak()
HiddenMethodDebuggerBreak();
VisibleMethodDebuggerBreak();
}

[System.Diagnostics.DebuggerStepThroughAttribute]
public static void NotStopOnJustMyCode()
{
var a = 0;
currentCount++;
var b = 1;
}

[System.Diagnostics.DebuggerStepThroughAttribute]
public static void NotStopOnJustMyCodeUserBp()
{
System.Diagnostics.Debugger.Break();
}

public static void RunStepThrough()
{
NotStopOnJustMyCode();
NotStopOnJustMyCodeUserBp();
}
}

public class DebugTypeFull
Expand Down