Skip to content

Commit

Permalink
[wasm] debug with modularized runtime(s) (#61848)
Browse files Browse the repository at this point in the history
* New globalThis.getDotnetRuntime method, which takes runtimeId, essentially index of the runtime on the page. It returns the DotNetPublicAPI object of the instance {MONO, BINDING, Module, RuntimeId, RuntimeBuildInfo }.
* Change the debugger to use the getDotnetRuntime() function when talking to the runtime on the page via CDP.
* Use getDotnetRuntime() in unit tests, with runtimeId is zero as there is only one runtime in tests.
* We add optional &runtimeId=0 to the initial URL which opens DebuggerProxy, so that MonoProxy could be created for specific runtime on the page.
* Moved mono_wasm_add_dbg_command_received, mono_wasm_debugger_log and mono_wasm_trace_logger out of C macro into typescript
* Introduced RuntimeBuildInfo: { ProductVersion, Configuration } into DotNetPublicAPI
  • Loading branch information
pavelsavara committed Nov 25, 2021
1 parent 337cb2f commit c9ea14c
Show file tree
Hide file tree
Showing 22 changed files with 239 additions and 151 deletions.
33 changes: 10 additions & 23 deletions src/mono/mono/component/mini-wasm-debugger.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ static gboolean has_pending_lazy_loaded_assemblies;

#define THREAD_TO_INTERNAL(thread) (thread)->internal_thread

extern void mono_wasm_debugger_log (int level, char *message);

void wasm_debugger_log (int level, const gchar *format, ...)
{
va_list args;
Expand All @@ -62,19 +64,7 @@ void wasm_debugger_log (int level, const gchar *format, ...)
va_start (args, format);
mesg = g_strdup_vprintf (format, args);
va_end (args);

EM_ASM ({
var level = $0;
var message = Module.UTF8ToString ($1);
var namespace = "Debugger.Debug";

if (INTERNAL["logging"] && INTERNAL.logging["debugger"]) {
INTERNAL.logging.debugger (level, message);
return;
}

console.debug("%s: %s", namespace, message);
}, level, mesg);
mono_wasm_debugger_log(level, mesg);
g_free (mesg);
}

Expand Down Expand Up @@ -367,16 +357,16 @@ mono_wasm_set_is_debugger_attached (gboolean is_attached)
}
}

extern void mono_wasm_add_dbg_command_received(mono_bool res_ok, int id, void* buffer, int buffer_len);

EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_send_dbg_command_with_parms (int id, MdbgProtCommandSet command_set, int command, guint8* data, unsigned int size, int valtype, char* newvalue)
{
MdbgProtBuffer bufWithParms;
buffer_init (&bufWithParms, 128);
m_dbgprot_buffer_add_data (&bufWithParms, data, size);
if (!write_value_to_buffer(&bufWithParms, valtype, newvalue)) {
EM_ASM ({
INTERNAL.mono_wasm_add_dbg_command_received ($0, $1, $2, $3);
}, 0, id, 0, 0);
mono_wasm_add_dbg_command_received(0, id, 0, 0);
return TRUE;
}
mono_wasm_send_dbg_command(id, command_set, command, bufWithParms.buf, m_dbgprot_buffer_len(&bufWithParms));
Expand All @@ -402,20 +392,17 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command,
}
else
error = mono_process_dbg_packet (id, command_set, command, &no_reply, data, data + size, &buf);
EM_ASM ({
INTERNAL.mono_wasm_add_dbg_command_received ($0, $1, $2, $3);
}, error == MDBGPROT_ERR_NONE, id, buf.buf, buf.p-buf.buf);


mono_wasm_add_dbg_command_received(error == MDBGPROT_ERR_NONE, id, buf.buf, buf.p-buf.buf);

buffer_free (&buf);
return TRUE;
}

static gboolean
receive_debugger_agent_message (void *data, int len)
{
EM_ASM ({
INTERNAL.mono_wasm_add_dbg_command_received (1, -1, $0, $1);
}, data, len);
mono_wasm_add_dbg_command_received(1, -1, data, len);
mono_wasm_save_thread_context();
mono_wasm_fire_debugger_agent_message ();
return FALSE;
Expand Down
8 changes: 7 additions & 1 deletion src/mono/wasm/debugger/BrowserDebugHost/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ async Task ConnectProxy(HttpContext context)
}
var endpoint = new Uri($"ws://{devToolsHost.Authority}{context.Request.Path}");
int runtimeId = 0;
if (context.Request.Query.TryGetValue("RuntimeId", out StringValues runtimeIdValue) &&
int.TryParse(runtimeIdValue.FirstOrDefault(), out int parsedId))
{
runtimeId = parsedId;
}
try
{
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
Expand All @@ -159,7 +165,7 @@ async Task ConnectProxy(HttpContext context)
);
context.Request.Query.TryGetValue("urlSymbolServer", out StringValues urlSymbolServerList);
var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList());
var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList(), runtimeId);
System.Net.WebSockets.WebSocket ideSocket = await context.WebSockets.AcceptWebSocketAsync();
Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public class DebuggerProxy
{
private readonly MonoProxy proxy;

public DebuggerProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList)
public DebuggerProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList, int runtimeId = 0)
{
proxy = new MonoProxy(loggerFactory, urlSymbolServerList);
proxy = new MonoProxy(loggerFactory, urlSymbolServerList, runtimeId);
}

public Task Run(Uri browserUri, WebSocket ideSocket)
Expand Down
24 changes: 12 additions & 12 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,31 +172,31 @@ internal class MonoCommands

public MonoCommands(string expression) => this.expression = expression;

public static MonoCommands GetDebuggerAgentBufferReceived() => new MonoCommands("INTERNAL.mono_wasm_get_dbg_command_info()");
public static MonoCommands GetDebuggerAgentBufferReceived(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_dbg_command_info()");

public static MonoCommands IsRuntimeReady() => new MonoCommands("INTERNAL.mono_wasm_runtime_is_ready");
public static MonoCommands IsRuntimeReady(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_runtime_is_ready");

public static MonoCommands GetLoadedFiles() => new MonoCommands("INTERNAL.mono_wasm_get_loaded_files()");
public static MonoCommands GetLoadedFiles(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_loaded_files()");

public static MonoCommands SendDebuggerAgentCommand(int id, int command_set, int command, string command_parameters)
public static MonoCommands SendDebuggerAgentCommand(int runtimeId, int id, int command_set, int command, string command_parameters)
{
return new MonoCommands($"INTERNAL.mono_wasm_send_dbg_command ({id}, {command_set}, {command},'{command_parameters}')");
return new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_send_dbg_command ({id}, {command_set}, {command},'{command_parameters}')");
}

public static MonoCommands SendDebuggerAgentCommandWithParms(int id, int command_set, int command, string command_parameters, int len, int type, string parm)
public static MonoCommands SendDebuggerAgentCommandWithParms(int runtimeId, int id, int command_set, int command, string command_parameters, int len, int type, string parm)
{
return new MonoCommands($"INTERNAL.mono_wasm_send_dbg_command_with_parms ({id}, {command_set}, {command},'{command_parameters}', {len}, {type}, '{parm}')");
return new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_send_dbg_command_with_parms ({id}, {command_set}, {command},'{command_parameters}', {len}, {type}, '{parm}')");
}

public static MonoCommands CallFunctionOn(JToken args) => new MonoCommands($"INTERNAL.mono_wasm_call_function_on ({args})");
public static MonoCommands CallFunctionOn(int runtimeId, JToken args) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_call_function_on ({args})");

public static MonoCommands GetDetails(int objectId, JToken args = null) => new MonoCommands($"INTERNAL.mono_wasm_get_details ({objectId}, {(args ?? "{ }")})");
public static MonoCommands GetDetails(int runtimeId, int objectId, JToken args = null) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_details ({objectId}, {(args ?? "{ }")})");

public static MonoCommands Resume() => new MonoCommands($"INTERNAL.mono_wasm_debugger_resume ()");
public static MonoCommands Resume(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_debugger_resume ()");

public static MonoCommands DetachDebugger() => new MonoCommands($"INTERNAL.mono_wasm_detach_debugger()");
public static MonoCommands DetachDebugger(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_detach_debugger()");

public static MonoCommands ReleaseObject(DotnetObjectId objectId) => new MonoCommands($"INTERNAL.mono_wasm_release_object('{objectId}')");
public static MonoCommands ReleaseObject(int runtimeId, DotnetObjectId objectId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_release_object('{objectId}')");
}

internal enum MonoErrorCodes
Expand Down
27 changes: 15 additions & 12 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ internal class MonoProxy : DevToolsProxy
private Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
private const string sPauseOnUncaught = "pause_on_uncaught";
private const string sPauseOnCaught = "pause_on_caught";
// index of the runtime in a same JS page/process
public int RuntimeId { get; private init; }

public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList) : base(loggerFactory)
public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList, int runtimeId = 0) : base(loggerFactory)
{
this.urlSymbolServerList = urlSymbolServerList ?? new List<string>();
RuntimeId = runtimeId;
}

internal ExecutionContext GetContext(SessionId sessionId)
Expand Down Expand Up @@ -223,7 +226,7 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth

case "Target.targetDestroyed":
{
await SendMonoCommand(sessionId, MonoCommands.DetachDebugger(), token);
await SendMonoCommand(sessionId, MonoCommands.DetachDebugger(RuntimeId), token);
break;
}
}
Expand All @@ -236,7 +239,7 @@ private async Task<bool> IsRuntimeAlreadyReadyAlready(SessionId sessionId, Cance
if (contexts.TryGetValue(sessionId, out ExecutionContext context) && context.IsRuntimeReady)
return true;

Result res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(), token);
Result res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(RuntimeId), token);
return res.Value?["result"]?["value"]?.Value<bool>() ?? false;
}

Expand Down Expand Up @@ -434,7 +437,7 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
if (!(DotnetObjectId.TryParse(args["objectId"], out DotnetObjectId objectId) && objectId.Scheme == "cfo_res"))
break;

await SendMonoCommand(id, MonoCommands.ReleaseObject(objectId), token);
await SendMonoCommand(id, MonoCommands.ReleaseObject(RuntimeId, objectId), token);
SendResponse(id, Result.OkFromObject(new { }), token);
return true;
}
Expand Down Expand Up @@ -558,7 +561,7 @@ private async Task<bool> CallOnFunction(MessageId id, JObject args, Cancellation
break;
case "cfo_res":
{
Result cfo_res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token);
Result cfo_res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(RuntimeId, args), token);
cfo_res = Result.OkFromObject(new { result = cfo_res.Value?["result"]?["value"]});
SendResponse(id, cfo_res, token);
return true;
Expand All @@ -574,7 +577,7 @@ private async Task<bool> CallOnFunction(MessageId id, JObject args, Cancellation
default:
return false;
}
Result res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token);
Result res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(RuntimeId, args), token);
if (res.IsErr)
{
SendResponse(id, res, token);
Expand Down Expand Up @@ -652,7 +655,7 @@ internal async Task<JToken> RuntimeGetPropertiesInternal(SessionId id, DotnetObj
return new JArray{await context.SdbAgent.GetPointerContent(int.Parse(objectId.Value), token)};
case "cfo_res":
{
Result res = await SendMonoCommand(id, MonoCommands.GetDetails(int.Parse(objectId.Value), args), token);
Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, int.Parse(objectId.Value), args), token);
string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value<string>();
return value_json_str != null ? JArray.Parse(value_json_str) : null;
}
Expand Down Expand Up @@ -831,7 +834,7 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
}
private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, CancellationToken token)
{
Result res = await SendMonoCommand(sessionId, MonoCommands.GetDebuggerAgentBufferReceived(), token);
Result res = await SendMonoCommand(sessionId, MonoCommands.GetDebuggerAgentBufferReceived(RuntimeId), token);
if (res.IsErr)
return false;

Expand Down Expand Up @@ -966,11 +969,11 @@ private async Task OnDefaultContext(SessionId sessionId, ExecutionContext contex

private async Task OnResume(MessageId msg_id, CancellationToken token)
{
ExecutionContext ctx = GetContext(msg_id);
if (ctx.CallStack != null)
ExecutionContext context = GetContext(msg_id);
if (context.CallStack != null)
{
// Stopped on managed code
await SendMonoCommand(msg_id, MonoCommands.Resume(), token);
await SendMonoCommand(msg_id, MonoCommands.Resume(RuntimeId), token);
}

//discard managed frames
Expand Down Expand Up @@ -1187,7 +1190,7 @@ internal async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken

if (loaded_files == null)
{
Result loaded = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(), token);
Result loaded = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(RuntimeId), token);
loaded_files = loaded.Value?["result"]?["value"]?.ToObject<string[]>();
}

Expand Down
4 changes: 2 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ public async Task<bool> EnableReceiveRequests(EventKind eventKind, CancellationT
}

internal async Task<MonoBinaryReader> SendDebuggerAgentCommand<T>(T command, MonoBinaryWriter arguments, CancellationToken token) =>
MonoBinaryReader.From (await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommand(GetId(), (int)GetCommandSetForCommand(command), (int)(object)command, arguments?.ToBase64().data ?? string.Empty), token));
MonoBinaryReader.From (await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommand(proxy.RuntimeId, GetId(), (int)GetCommandSetForCommand(command), (int)(object)command, arguments?.ToBase64().data ?? string.Empty), token));

internal CommandSet GetCommandSetForCommand<T>(T command) =>
command switch {
Expand All @@ -882,7 +882,7 @@ internal CommandSet GetCommandSetForCommand<T>(T command) =>
};

internal async Task<MonoBinaryReader> SendDebuggerAgentCommandWithParms<T>(T command, (string data, int length) encoded, int type, string extraParm, CancellationToken token) =>
MonoBinaryReader.From(await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommandWithParms(GetId(), (int)GetCommandSetForCommand(command), (int)(object)command, encoded.data, encoded.length, type, extraParm), token));
MonoBinaryReader.From(await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommandWithParms(proxy.RuntimeId, GetId(), (int)GetCommandSetForCommand(command), (int)(object)command, encoded.data, encoded.length, type, extraParm), token));

public async Task<int> CreateString(string value, CancellationToken token)
{
Expand Down
14 changes: 7 additions & 7 deletions src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task CreateJSBreakpoint()
{
// Test that js breakpoints get set correctly
// 13 24
// 13 33
// 13 53
var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24);

Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString());
Expand All @@ -48,7 +48,7 @@ public async Task CreateJSBreakpoint()
Assert.Equal(13, (int)loc["lineNumber"]);
Assert.Equal(24, (int)loc["columnNumber"]);

var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 33);
var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 53);

Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString());
Assert.Equal(1, bp2_res.Value["locations"]?.Value<JArray>()?.Count);
Expand All @@ -57,14 +57,14 @@ public async Task CreateJSBreakpoint()

Assert.NotNull(loc2["scriptId"]);
Assert.Equal(13, (int)loc2["lineNumber"]);
Assert.Equal(33, (int)loc2["columnNumber"]);
Assert.Equal(53, (int)loc2["columnNumber"]);
}

[Fact]
public async Task CreateJS0Breakpoint()
{
// 13 24
// 13 33
// 13 53
var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0);

Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString());
Expand All @@ -74,9 +74,9 @@ public async Task CreateJS0Breakpoint()

Assert.NotNull(loc["scriptId"]);
Assert.Equal(13, (int)loc["lineNumber"]);
Assert.Equal(24, (int)loc["columnNumber"]);
Assert.Equal(4, (int)loc["columnNumber"]);

var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 33);
var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 53);

Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString());
Assert.Equal(1, bp2_res.Value["locations"]?.Value<JArray>()?.Count);
Expand All @@ -85,7 +85,7 @@ public async Task CreateJS0Breakpoint()

Assert.NotNull(loc2["scriptId"]);
Assert.Equal(13, (int)loc2["lineNumber"]);
Assert.Equal(33, (int)loc2["columnNumber"]);
Assert.Equal(53, (int)loc2["columnNumber"]);
}

[Theory]
Expand Down
Loading

0 comments on commit c9ea14c

Please sign in to comment.