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 @@ -5673,24 +5673,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 @@ -9009,7 +8991,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 @@ -9040,7 +9022,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);
}

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
190 changes: 190 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,8 @@ 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);
EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_set_value_on_object (int object_id, 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 @@ -862,6 +864,16 @@ typedef struct {
gboolean found;
} FrameDescData;


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

/*
* this returns a string formatted like
*
Expand Down Expand Up @@ -1514,6 +1526,111 @@ 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)
{
char* endptr;
errno = 0;
switch (t->type) {
case MONO_TYPE_BOOLEAN:
if (!strcmp(variableValue, "True"))
*(guint8*)addr = 1;
else if (!strcmp(variableValue, "False"))
*(guint8*)addr = 0;
else
return FALSE;
break;
case MONO_TYPE_CHAR:
*(gunichar2*)addr = variableValue[0];
break;
case MONO_TYPE_I1:
*(gint8*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_U1:
*(guint8*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_I2:
*(gint16*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_U2:
*(guint16*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_I4:
*(gint32*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_U4:
*(guint32*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_I8:
*(gint64*)addr = strtoimax(variableValue, &endptr, 10);
break;
case MONO_TYPE_U8:
*(guint64*)addr = strtol(variableValue, &endptr, 10);
break;
case MONO_TYPE_R4:
*(gfloat*)addr = strtof(variableValue, &endptr);
break;
case MONO_TYPE_R8:
*(gdouble*)addr = strtof(variableValue, &endptr);
}
if (errno != 0)
return FALSE;
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 = NULL;
guint8 *val_buf = NULL;

++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);
MonoMethodHeader *header = mono_method_get_header_checked (method, error);
int pos = data->pos;

if (pos < 0) {
pos = - pos - 1;
is_arg = TRUE;
t = sig->params [pos];
}
else {
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)));

if (!decode_value(t, val_buf, data->new_value)) {
data->error = TRUE;
return TRUE;
}

mono_de_set_interp_var (t, addr, val_buf);
return TRUE;
}

static gboolean
describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointer ud)
{
Expand Down Expand Up @@ -1552,6 +1669,23 @@ 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)
{
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;
data.error = FALSE;

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

EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass)
Expand Down Expand Up @@ -1611,6 +1745,62 @@ mono_wasm_invoke_getter_on_object (int object_id, const char* name)
return invoke_getter (obj, mono_object_class (obj), name);
}

EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_set_value_on_object (int object_id, const char* name, const char* value)
{
PRINT_DEBUG_MSG (1, "mono_wasm_set_value_on_object %d, name: %s, value: %s\n", object_id, name, value);
MonoObject *obj = get_object_from_id (object_id);

if (!obj || !name) {
PRINT_DEBUG_MSG (2, "mono_wasm_set_value_on_object: none of the arguments can be null");
return FALSE;
}
MonoClass* klass = mono_object_class (obj);

gpointer iter;
handle_parent:
iter = NULL;
MonoClassField *f;
while ((f = mono_class_get_fields_internal (klass, &iter))) {
if (!f->name || strcasecmp (f->name, name) != 0)
continue;
guint8 *val_buf = (guint8 *)g_alloca (mono_class_instance_size (mono_class_from_mono_type_internal (f->type)));

if (!decode_value(f->type, val_buf, value)) {
return FALSE;
}
mono_de_set_interp_var (f->type, (guint8*)obj + f->offset, val_buf);
return TRUE;
}

iter = NULL;
MonoProperty *p;
MonoObject *exc;
ERROR_DECL (error);
while ((p = mono_class_get_properties (klass, &iter))) {
if (!p->name || strcasecmp (p->name, name) != 0)
continue;
if (!p->set)
continue;
MonoType *type = mono_method_signature_internal(p->set)->params[0];
guint8 *val_buf = (guint8 *)g_alloca (mono_class_instance_size (mono_class_from_mono_type_internal (type)));

if (!decode_value(type, val_buf, value)) {
return FALSE;
}
mono_runtime_try_invoke (p->set, obj, (void **)&val_buf, &exc, error);
if (!is_ok (error) && exc == NULL)
exc = (MonoObject*) mono_error_convert_to_exception (error);
if (exc)
return FALSE;
return TRUE;
}

if ((klass = m_class_get_parent(klass)))
goto handle_parent;
return FALSE;
}

EMSCRIPTEN_KEEPALIVE gboolean
mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name)
{
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
30 changes: 30 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,20 @@ 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);
var varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
var varToSetValue = varIds.FirstOrDefault(v => v.Name == varName);
Result res = await SendMonoCommand(id, MonoCommands.SetVariableValue(scopeId, varToSetValue.Index, varName, varValue["value"].Value<string>()), token);
if (res.Value["result"]?["value"].Value<int>() == 1)
SendResponse(id, Result.Ok(new JObject()), token);
else
SendResponse(id, Result.Err($"Unable to set '{varValue["value"].Value<string>()}' to variable '{varName}'"), token);
return true;
}

private async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
if (objectId.Scheme == "scope")
Expand Down
25 changes: 25 additions & 0 deletions src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,21 @@ internal async Task<Result> InvokeGetter(JToken obj, object arguments, string fn
return res;
}

internal async Task<Result> SetValueOnObject(JToken obj, string property, string newvalue, string fn = "function(a, b) { this[a] = b; }", bool expect_ok = true)
{
var req = JObject.FromObject(new
{
functionDeclaration = fn,
objectId = obj["value"]?["objectId"]?.Value<string>(),
arguments = new[] { new { value = property } , new { value = newvalue } },
silent = true
});
var res = await cli.SendCommand("Runtime.callFunctionOn", req, token);
Assert.True(expect_ok == res.IsOk, $"SetValueOnObject failed for {req} with {res}");

return res;
}

internal async Task<JObject> StepAndCheck(StepKind kind, string script_loc, int line, int column, string function_name,
Func<JObject, Task> wait_for_event_fn = null, Action<JToken> locals_fn = null, int times = 1)
{
Expand Down Expand Up @@ -789,6 +804,16 @@ internal async Task<JToken> GetProperties(string id, JToken fn_args = null, bool
return (null, res);
}

internal async Task<(JToken, Result)> SetVariableValueOnCallFrame(JObject parms, bool expect_ok = true)
{
var res = await cli.SendCommand("Debugger.setVariableValue", parms, token);
AssertEqual(expect_ok, res.IsOk, $"Debugger.setVariableValue ('{parms}') returned {res.IsOk} instead of {expect_ok}, with Result: {res}");
if (res.IsOk)
return (res.Value["result"], res);

return (null, res);
}

internal async Task<Result> RemoveBreakpoint(string id, bool expect_ok = true)
{
var remove_bp = JObject.FromObject(new
Expand Down
Loading