Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm][debugger] Implement support to modify values. #49557

Merged
merged 13 commits into from
Apr 16, 2021
22 changes: 2 additions & 20 deletions src/mono/mono/mini/debugger-agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -5655,24 +5655,6 @@ set_var (MonoType *t, MonoDebugVarInfo *var, MonoContext *ctx, MonoDomain *domai
}
}

static void
set_interp_var (MonoType *t, gpointer addr, guint8 *val_buf)
{
int size;

if (t->byref) {
addr = *(gpointer*)addr;
g_assert (addr);
}

if (MONO_TYPE_IS_REFERENCE (t))
size = sizeof (gpointer);
else
size = mono_class_value_size (mono_class_from_mono_type_internal (t), NULL);

memcpy (addr, val_buf, size);
}

static void
clear_event_request (int req_id, int etype)
{
Expand Down Expand Up @@ -8965,7 +8947,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
addr = (guint8*)mini_get_interp_callbacks ()->frame_get_arg (frame->interp_frame, pos);
else
addr = (guint8*)mini_get_interp_callbacks ()->frame_get_local (frame->interp_frame, pos);
set_interp_var (t, addr, val_buf);
mono_de_set_interp_var (t, addr, val_buf);
} else {
set_var (t, var, &frame->ctx, frame->de.domain, val_buf, frame->reg_locations, &tls->restore_state.ctx);
}
Expand Down Expand Up @@ -8996,7 +8978,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
guint8 *addr;

addr = (guint8*)mini_get_interp_callbacks ()->frame_get_this (frame->interp_frame);
set_interp_var (m_class_get_this_arg (frame->actual_method->klass), addr, val_buf);
mono_de_set_interp_var (m_class_get_this_arg (frame->actual_method->klass), addr, val_buf);
} else {
var = jit->this_var;
if (!var) {
Expand Down
17 changes: 17 additions & 0 deletions src/mono/mono/mini/debugger-engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -1754,5 +1754,22 @@ get_notify_debugger_of_wait_completion_method (void)
return notify_debugger_of_wait_completion_method_cache;
}

void
mono_de_set_interp_var (MonoType *t, gpointer addr, guint8 *val_buf)
{
int size;

if (t->byref) {
addr = *(gpointer*)addr;
g_assert (addr);
radical marked this conversation as resolved.
Show resolved Hide resolved
}

if (MONO_TYPE_IS_REFERENCE (t))
size = sizeof (gpointer);
else
size = mono_class_value_size (mono_class_from_mono_type_internal (t), NULL);

memcpy (addr, val_buf, size);
}

#endif
2 changes: 2 additions & 0 deletions src/mono/mono/mini/debugger-engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@ DbgEngineErrorCode mono_de_ss_create (MonoInternalThread *thread, StepSize size,
void mono_de_cancel_ss (SingleStepReq *req);
void mono_de_cancel_all_ss (void);

void mono_de_set_interp_var (MonoType *t, gpointer addr, guint8 *val_buf);

gboolean set_set_notification_for_wait_completion_flag (DbgEngineStackFrame *frame);
MonoClass * get_class_to_get_builder_field(DbgEngineStackFrame *frame);
gpointer get_this_addr (DbgEngineStackFrame *the_frame);
Expand Down
116 changes: 116 additions & 0 deletions src/mono/mono/mini/mini-wasm-debugger.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_object (int object_id,
EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name);
EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass);
EMSCRIPTEN_KEEPALIVE void mono_wasm_set_is_debugger_attached (gboolean is_attached);
EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_set_variable_value_native (int scope, int index, const char* name, const char* value);

//JS functions imported that we use
extern void mono_wasm_add_frame (int il_offset, int method_token, int frame_id, const char *assembly_name, const char *method_name);
Expand Down Expand Up @@ -857,6 +858,15 @@ typedef struct {
gboolean found;
} FrameDescData;


typedef struct {
int cur_frame;
int target_frame;
int pos;
const char* new_value;
gboolean found;
} SetVariableValueData;

/*
* this returns a string formatted like
*
Expand Down Expand Up @@ -1490,6 +1500,96 @@ describe_variable (InterpFrame *frame, MonoMethod *method, MonoMethodHeader *hea
return describe_value(type, addr, gpflags);
}

static gboolean
decode_value (MonoType *t, guint8 *addr, const char* variableValue)
{
switch (t->type) {
case MONO_TYPE_BOOLEAN:
*(guint8*)addr = atoi(variableValue);
break;
case MONO_TYPE_CHAR:
*(gunichar2*)addr = variableValue[0];
break;
case MONO_TYPE_I1:
*(gint8*)addr = atoi(variableValue);
break;
case MONO_TYPE_U1:
*(guint8*)addr = atoi(variableValue);
break;
case MONO_TYPE_I2:
*(gint16*)addr = atoi(variableValue);
break;
case MONO_TYPE_U2:
*(guint16*)addr = atoi(variableValue);
break;
case MONO_TYPE_I4:
*(gint32*)addr = atoi(variableValue);
break;
case MONO_TYPE_U4:
*(guint32*)addr = atoi(variableValue);
break;
case MONO_TYPE_I8:
*(gint64*)addr = atoi(variableValue);
break;
case MONO_TYPE_U8:
*(guint64*)addr = atol(variableValue);
break;
case MONO_TYPE_R4:
*(guint32*)addr = atoi(variableValue);
thaystg marked this conversation as resolved.
Show resolved Hide resolved
break;
case MONO_TYPE_R8:
*(guint64*)addr = atol(variableValue);
thaystg marked this conversation as resolved.
Show resolved Hide resolved
}
radical marked this conversation as resolved.
Show resolved Hide resolved
return TRUE;
}

static gboolean
set_variable_value_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointer ud)
{
ERROR_DECL (error);
SetVariableValueData *data = (SetVariableValueData*)ud;
gboolean is_arg = FALSE;
MonoType *t;
guint8 *val_buf;

++data->cur_frame;

//skip wrappers
if (info->type != FRAME_TYPE_MANAGED && info->type != FRAME_TYPE_INTERP) {
return FALSE;
}

if (data->cur_frame != data->target_frame)
return FALSE;

data->found = TRUE;

InterpFrame *frame = (InterpFrame*)info->interp_frame;
MonoMethod *method = frame->imethod->method;
MonoMethodSignature *sig = mono_method_signature_internal (method);
radical marked this conversation as resolved.
Show resolved Hide resolved
MonoMethodHeader *header = mono_method_get_header_checked (method, error);
radical marked this conversation as resolved.
Show resolved Hide resolved
int pos = data->pos;

if (pos < 0) {
pos = - pos - 1;
is_arg = TRUE;
t = sig->params [pos];
radical marked this conversation as resolved.
Show resolved Hide resolved
}
else {
radical marked this conversation as resolved.
Show resolved Hide resolved
t = header->locals [pos];
}

guint8 *addr;
if (is_arg)
addr = (guint8*)mini_get_interp_callbacks ()->frame_get_arg (frame, pos);
else
addr = (guint8*)mini_get_interp_callbacks ()->frame_get_local (frame, pos);
val_buf = (guint8 *)g_alloca (mono_class_instance_size (mono_class_from_mono_type_internal (t)));
decode_value(t, val_buf, data->new_value);
mono_de_set_interp_var (t, addr, val_buf);
radical marked this conversation as resolved.
Show resolved Hide resolved
return TRUE;
}

static gboolean
describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointer ud)
{
Expand Down Expand Up @@ -1528,6 +1628,22 @@ describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointe
mono_metadata_free_mh (header);
return TRUE;
}
EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_set_variable_value_native (int scope, int index, const char* name, const char* value)
radical marked this conversation as resolved.
Show resolved Hide resolved
{
if (scope < 0)
return FALSE;

SetVariableValueData data;
data.target_frame = scope;
data.cur_frame = -1;
data.pos = index;
data.found = FALSE;
data.new_value = value;

mono_walk_stack_with_ctx (set_variable_value_on_frame, NULL, MONO_UNWIND_NONE, &data);
return data.found;
}

EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass)
Expand Down
5 changes: 5 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ public static MonoCommands GetScopeVariables(int scopeId, params VarInfo[] vars)
return new MonoCommands($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(var_ids)})");
}

public static MonoCommands SetVariableValue(int scopeId, int index, string name, string newValue)
{
return new MonoCommands($"MONO.mono_wasm_set_variable_value({scopeId}, {index}, '{name}', '{newValue}')");
}

public static MonoCommands EvaluateMemberAccess(int scopeId, string expr, params VarInfo[] vars)
{
var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray();
Expand Down
27 changes: 27 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,22 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
{
return await Step(id, StepKind.Into, token);
}
case "Debugger.setVariableValue":
{
if (!DotnetObjectId.TryParse(args?["callFrameId"], out DotnetObjectId objectId))
return false;
switch (objectId.Scheme)
{
case "scope":
return await OnSetVariableValue(id,
int.Parse(objectId.Value),
args?["variableName"]?.Value<string>(),
args?["newValue"],
token);
default:
return false;
}
}

case "Debugger.stepOut":
{
Expand Down Expand Up @@ -470,6 +486,17 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
return false;
}

private async Task<bool> OnSetVariableValue(MessageId id, int scopeId, string varName, JToken varValue, CancellationToken token)
{
ExecutionContext ctx = GetContext(id);
Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
radical marked this conversation as resolved.
Show resolved Hide resolved
var varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
var varToSetValue = varIds.FirstOrDefault(v => v.Name == varName);
radical marked this conversation as resolved.
Show resolved Hide resolved
Result res = await SendMonoCommand(id, MonoCommands.SetVariableValue(scopeId, varToSetValue.Index, varName, varValue["value"].Value<string>()), token);
SendResponse(id, Result.Ok(new JObject()), token);
return true;
}

private async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
if (objectId.Scheme == "scope")
Expand Down
20 changes: 16 additions & 4 deletions src/mono/wasm/runtime/library_mono.js
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,15 @@ var MonoSupportLib = {
throw new Error(`Could not get a value for ${root}`);

return this._resolve_member_by_name(rootObject, root, parts);
},

mono_wasm_set_variable_value: function (scope, index, name, newValue) {
console.debug (">> mono_wasm_set_variable_value " + name + " - " + newValue);
if (!this.mono_wasm_set_variable_value_native)
this.mono_wasm_set_variable_value_native = Module.cwrap ("mono_wasm_set_variable_value_native", 'number', ['number', 'number', 'string', 'string']);
radical marked this conversation as resolved.
Show resolved Hide resolved

var ret = this.mono_wasm_set_variable_value_native(scope, index, name, newValue);
return ret;
},

/**
Expand Down Expand Up @@ -1904,7 +1913,7 @@ var MonoSupportLib = {

if (invariantMode)
this.mono_wasm_setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1");

// Set globalization mode to PredefinedCulturesOnly
this.mono_wasm_setenv ("DOTNET_SYSTEM_GLOBALIZATION_PREDEFINED_CULTURES_ONLY", "1");
},
Expand Down Expand Up @@ -2099,7 +2108,8 @@ var MonoSupportLib = {
type: "boolean",
value: v,
description: v.toString ()
}
},
writable:true
});
break;
}
Expand All @@ -2111,7 +2121,8 @@ var MonoSupportLib = {
type: "symbol",
value: v,
description: v
}
},
writable:true
});
break;
}
Expand All @@ -2122,7 +2133,8 @@ var MonoSupportLib = {
type: "number",
value: value,
description: '' + value
}
},
writable:true
});
break;

Expand Down