diff --git a/src/mono/mono/mini/debugger-agent.c b/src/mono/mono/mini/debugger-agent.c index 593cdff22298d1..33b4586f2b2807 100644 --- a/src/mono/mono/mini/debugger-agent.c +++ b/src/mono/mono/mini/debugger-agent.c @@ -109,8 +109,7 @@ #define DISABLE_SOCKET_TRANSPORT #endif -#ifndef DISABLE_SDB - +#if !defined (DISABLE_SDB) || defined(TARGET_WASM) #include #include @@ -145,28 +144,6 @@ typedef struct { gboolean using_icordbg; } AgentConfig; -typedef struct _InvokeData InvokeData; - -struct _InvokeData -{ - int id; - int flags; - guint8 *p; - guint8 *endp; - /* This is the context which needs to be restored after the invoke */ - MonoContext ctx; - gboolean has_ctx; - /* - * If this is set, invoke this method with the arguments given by ARGS. - */ - MonoMethod *method; - gpointer *args; - guint32 suspend_count; - int nmethods; - - InvokeData *last_invoke; -}; - struct _DebuggerTlsData { MonoThreadUnwindState context; @@ -262,15 +239,6 @@ struct _DebuggerTlsData { gboolean gc_finalizing; }; -typedef struct { - const char *name; - void (*connect) (const char *address); - void (*close1) (void); - void (*close2) (void); - gboolean (*send) (void *buf, int len); - int (*recv) (void *buf, int len); -} DebuggerTransport; - /* Buffered reply packets */ static ReplyPacket reply_packets [128]; static int nreply_packets; @@ -314,7 +282,9 @@ typedef struct { /* * Globals */ - +#ifdef TARGET_WASM +static DebuggerTlsData debugger_wasm_thread; +#endif static AgentConfig agent_config; /* @@ -397,6 +367,28 @@ static gint32 suspend_count; /* Whenever to buffer reply messages and send them together */ static gboolean buffer_replies; + +#ifndef TARGET_WASM +#define GET_TLS_DATA_FROM_THREAD(thread) \ + DebuggerTlsData *tls = NULL; \ + mono_loader_lock(); \ + if (thread_to_tls != NULL) \ + tls = (DebuggerTlsData*)mono_g_hash_table_lookup(thread_to_tls, thread); \ + mono_loader_unlock(); +#define GET_DEBUGGER_TLS() \ + DebuggerTlsData *tls; \ + tls = (DebuggerTlsData *)mono_native_tls_get_value (debugger_tls_id); +#else +#define GET_TLS_DATA_FROM_THREAD(thread) \ + DebuggerTlsData *tls; \ + tls = &debugger_wasm_thread; +#define GET_DEBUGGER_TLS() \ + DebuggerTlsData *tls; \ + tls = &debugger_wasm_thread; +#endif + +//mono_native_tls_get_value (debugger_tls_id); + #define dbg_lock mono_de_lock #define dbg_unlock mono_de_unlock @@ -460,6 +452,7 @@ static void objrefs_init (void); static void objrefs_cleanup (void); static void ids_init (void); + static void ids_cleanup (void); static void suspend_init (void); @@ -478,19 +471,13 @@ static MonoContext* tls_get_restore_state (void *the_tls); static gboolean try_process_suspend (void *tls, MonoContext *ctx, gboolean from_breakpoint); static gboolean begin_breakpoint_processing (void *tls, MonoContext *ctx, MonoJitInfo *ji, gboolean from_signal); static void begin_single_step_processing (MonoContext *ctx, gboolean from_signal); -static void ss_discard_frame_context (void *the_tls); -static void ss_calculate_framecount (void *tls, MonoContext *ctx, gboolean force_use_ctx, DbgEngineStackFrame ***frames, int *nframes); static gboolean ensure_jit (DbgEngineStackFrame* the_frame); static int ensure_runtime_is_suspended (void); -static int get_this_async_id (DbgEngineStackFrame *frame); -static void* create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, EventKind kind); -static void process_breakpoint_events (void *_evts, MonoMethod *method, MonoContext *ctx, int il_offset); -static int ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *args); -static void ss_args_destroy (SingleStepArgs *ss_args); static int handle_multiple_ss_requests (void); static GENERATE_TRY_GET_CLASS_WITH_CACHE (fixed_buffer, "System.Runtime.CompilerServices", "FixedBufferAttribute") + #ifndef DISABLE_SOCKET_TRANSPORT static void register_socket_transport (void); @@ -710,17 +697,17 @@ debugger_agent_init (void) cbs.try_process_suspend = try_process_suspend; cbs.begin_breakpoint_processing = begin_breakpoint_processing; cbs.begin_single_step_processing = begin_single_step_processing; - cbs.ss_discard_frame_context = ss_discard_frame_context; - cbs.ss_calculate_framecount = ss_calculate_framecount; + cbs.ss_discard_frame_context = mono_ss_discard_frame_context; + cbs.ss_calculate_framecount = mono_ss_calculate_framecount; cbs.ensure_jit = ensure_jit; cbs.ensure_runtime_is_suspended = ensure_runtime_is_suspended; - cbs.get_this_async_id = get_this_async_id; + cbs.get_this_async_id = mono_get_this_async_id; cbs.set_set_notification_for_wait_completion_flag = set_set_notification_for_wait_completion_flag; cbs.get_notify_debugger_of_wait_completion_method = get_notify_debugger_of_wait_completion_method; - cbs.create_breakpoint_events = create_breakpoint_events; - cbs.process_breakpoint_events = process_breakpoint_events; - cbs.ss_create_init_args = ss_create_init_args; - cbs.ss_args_destroy = ss_args_destroy; + cbs.create_breakpoint_events = mono_dbg_create_breakpoint_events; + cbs.process_breakpoint_events = mono_dbg_process_breakpoint_events; + cbs.ss_create_init_args = mono_ss_create_init_args; + cbs.ss_args_destroy = mono_ss_args_destroy; cbs.handle_multiple_ss_requests = handle_multiple_ss_requests; mono_de_init (&cbs); @@ -1287,9 +1274,6 @@ static DebuggerTransport *transport; static DebuggerTransport transports [MAX_TRANSPORTS]; static int ntransports; -MONO_API void -mono_debugger_agent_register_transport (DebuggerTransport *trans); - void mono_debugger_agent_register_transport (DebuggerTransport *trans) { @@ -1590,6 +1574,24 @@ static GHashTable *obj_to_objref; /* Protected by the dbg lock */ static MonoGHashTable *suspended_objs; +#ifdef TARGET_WASM +void mono_init_debugger_agent_for_wasm (int log_level_parm) +{ + if (mono_atomic_cas_i32 (&agent_inited, 1, 0) == 1) + return; + + ids_init(); + objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref); + obj_to_objref = g_hash_table_new (NULL, NULL); + + log_level = log_level; + event_requests = g_ptr_array_new (); + vm_start_event_sent = TRUE; + transport = &transports [0]; + memset(&debugger_wasm_thread, 0, sizeof(DebuggerTlsData)); + agent_config.enabled = TRUE; +} +#endif static void @@ -1986,7 +1988,6 @@ static int buffer_add_ptr_id (Buffer *buf, MonoDomain *domain, IdType type, gpointer val) { int id = get_id (domain, type, val); - buffer_add_id (buf, id); return id; } @@ -2176,6 +2177,21 @@ save_thread_context (MonoContext *ctx) mono_thread_state_init_from_current (&tls->context); } +#ifdef TARGET_WASM +void +mono_wasm_save_thread_context (void) +{ + debugger_wasm_thread.really_suspended = TRUE; + mono_thread_state_init_from_current (&debugger_wasm_thread.context); +} + +DebuggerTlsData* +mono_wasm_get_tls (void) +{ + return &debugger_wasm_thread; +} +#endif + static MonoCoopMutex suspend_mutex; /* Cond variable used to wait for suspend_count becoming 0 */ @@ -2700,7 +2716,8 @@ static int count_threads_to_wait_for (void) { int count = 0; - + if (thread_to_tls == NULL) + return 0; mono_loader_lock (); mono_g_hash_table_foreach (thread_to_tls, count_thread, &count); mono_loader_unlock (); @@ -3049,7 +3066,7 @@ compute_frame_info (MonoInternalThread *thread, DebuggerTlsData *tls, gboolean f tls->frames = new_frames; tls->frame_count = new_frame_count; tls->frames_up_to_date = TRUE; - +#ifndef TARGET_WASM if (CHECK_PROTOCOL_VERSION (2, 52)) { MonoJitTlsData *jit_data = thread->thread_info->jit_data; gboolean has_interp_resume_state = FALSE; @@ -3064,6 +3081,7 @@ compute_frame_info (MonoInternalThread *thread, DebuggerTlsData *tls, gboolean f } } } +#endif } /* @@ -3494,7 +3512,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx return; } } - + if (event == EVENT_KIND_VM_START) suspend_policy = agent_config.suspend ? SUSPEND_POLICY_ALL : SUSPEND_POLICY_NONE; @@ -3552,12 +3570,10 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx break; case EVENT_KIND_BREAKPOINT: case EVENT_KIND_STEP: { - DebuggerTlsData *tls; - tls = (DebuggerTlsData *)mono_native_tls_get_value (debugger_tls_id); + GET_DEBUGGER_TLS(); g_assert (tls); mono_stopwatch_stop (&tls->step_time); MonoMethod *method = (MonoMethod *)arg; - buffer_add_methodid (&buf, domain, method); buffer_add_long (&buf, il_offset); break; @@ -3578,6 +3594,9 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx case EVENT_KIND_EXCEPTION: { EventInfo *ei = (EventInfo *)arg; buffer_add_objid (&buf, ei->exc); +#ifdef TARGET_WASM + buffer_add_byte (&buf, ei->caught); +#endif /* * We are not yet suspending, so get_objref () will not keep this object alive. So we need to do it * later after the suspension. (#12494). @@ -3586,8 +3605,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx break; } case EVENT_KIND_USER_BREAK: { - DebuggerTlsData *tls; - tls = (DebuggerTlsData *)mono_native_tls_get_value (debugger_tls_id); + GET_DEBUGGER_TLS(); g_assert (tls); // We are already processing a breakpoint event if (tls->disable_breakpoints) @@ -4042,14 +4060,18 @@ event_requests_cleanup (void) * * Ensure DebuggerTlsData fields are filled out. */ -static void -ss_calculate_framecount (void *the_tls, MonoContext *ctx, gboolean force_use_ctx, DbgEngineStackFrame ***frames, int *nframes) +void +mono_ss_calculate_framecount (void *the_tls, MonoContext *ctx, gboolean force_use_ctx, DbgEngineStackFrame ***frames, int *nframes) { DebuggerTlsData *tls = (DebuggerTlsData*)the_tls; - +#ifndef TARGET_WASM if (force_use_ctx || !tls->context.valid) mono_thread_state_init_from_monoctx (&tls->context, ctx); compute_frame_info (tls->thread, tls, FALSE); +#else + compute_frame_info (tls->thread, tls, TRUE); +#endif + if (frames) *frames = (DbgEngineStackFrame**)tls->frames; if (nframes) @@ -4061,8 +4083,8 @@ ss_calculate_framecount (void *the_tls, MonoContext *ctx, gboolean force_use_ctx * * Discard frame data and invalidate any context */ -static void -ss_discard_frame_context (void *the_tls) +void +mono_ss_discard_frame_context (void *the_tls) { DebuggerTlsData *tls = (DebuggerTlsData*)the_tls; tls->context.valid = FALSE; @@ -4107,8 +4129,8 @@ breakpoint_matches_assembly (MonoBreakpoint *bp, MonoAssembly *assembly) //This ID is used to figure out if breakpoint hit on resumeOffset belongs to us or not //since thread probably changed... -static int -get_this_async_id (DbgEngineStackFrame *frame) +int +mono_get_this_async_id (DbgEngineStackFrame *frame) { MonoClassField *builder_field; gpointer builder; @@ -4161,8 +4183,11 @@ begin_breakpoint_processing (void *the_tls, MonoContext *ctx, MonoJitInfo *ji, g * Skip the instruction causing the breakpoint signal. */ if (from_signal) +#ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED mono_arch_skip_breakpoint (ctx, ji); - +#else + NOT_IMPLEMENTED; +#endif if (tls->disable_breakpoints) return FALSE; return TRUE; @@ -4174,8 +4199,8 @@ typedef struct { int suspend_policy; } BreakPointEvents; -static void* -create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, EventKind kind) +void* +mono_dbg_create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, EventKind kind) { int suspend_policy = 0; BreakPointEvents *evts = g_new0 (BreakPointEvents, 1); @@ -4191,8 +4216,8 @@ create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *j return evts; } -static void -process_breakpoint_events (void *_evts, MonoMethod *method, MonoContext *ctx, int il_offset) +void +mono_dbg_process_breakpoint_events (void *_evts, MonoMethod *method, MonoContext *ctx, int il_offset) { BreakPointEvents *evts = (BreakPointEvents*)_evts; /* @@ -4301,8 +4326,8 @@ user_break_cb (StackFrameInfo *frame, MonoContext *ctx, gpointer user_data) /* * Called by System.Diagnostics.Debugger:Break (). */ -static void -debugger_agent_user_break (void) +void +mono_dbg_debugger_agent_user_break (void) { if (agent_config.enabled) { MonoContext ctx; @@ -4332,7 +4357,11 @@ static void begin_single_step_processing (MonoContext *ctx, gboolean from_signal) { if (from_signal) +#ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED mono_arch_skip_single_step (ctx); +#else + NOT_IMPLEMENTED; +#endif } static void @@ -4364,7 +4393,11 @@ debugger_agent_single_step_event (void *sigctx) MonoContext ctx; mono_sigctx_to_monoctx (sigctx, &ctx); +#ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED mono_arch_skip_single_step (&ctx); +#else + NOT_IMPLEMENTED; +#endif mono_monoctx_to_sigctx (&ctx, sigctx); return; } @@ -4444,8 +4477,8 @@ debugger_agent_breakpoint_from_context (MonoContext *ctx) if (MONO_CONTEXT_GET_IP (ctx) == orig_ip - 1) MONO_CONTEXT_SET_IP (ctx, orig_ip); } -static void -ss_args_destroy (SingleStepArgs *ss_args) +void +mono_ss_args_destroy (SingleStepArgs *ss_args) { if (ss_args->frames) free_frames ((StackFrame**)ss_args->frames, ss_args->nframes); @@ -4470,8 +4503,8 @@ ensure_runtime_is_suspended (void) return ERR_NONE; } -static int -ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *args) +int +mono_ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *args) { MonoSeqPointInfo *info = NULL; gboolean found_sp; @@ -4481,10 +4514,9 @@ ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *args) gboolean set_ip = FALSE; StackFrame **frames = NULL; int nframes = 0; - - mono_loader_lock (); - DebuggerTlsData *tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, ss_req->thread); - mono_loader_unlock (); + + GET_TLS_DATA_FROM_THREAD (ss_req->thread); + g_assert (tls); if (!tls->context.valid) { PRINT_DEBUG_MSG (1, "Received a single step request on a thread with no managed frames.\n"); @@ -4727,8 +4759,8 @@ debugger_agent_unhandled_exception (MonoException *exc) process_event (EVENT_KIND_EXCEPTION, &ei, 0, NULL, events, suspend_policy); } -static void -debugger_agent_handle_exception (MonoException *exc, MonoContext *throw_ctx, +void +mono_debugger_agent_handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame) { if (catch_ctx == NULL && catch_frame == NULL && mini_debug_options.suspend_on_unhandled && mono_object_class (exc) != mono_defaults.threadabortexception_class) { @@ -4736,23 +4768,15 @@ debugger_agent_handle_exception (MonoException *exc, MonoContext *throw_ctx, while (1) ; } - int i, j, suspend_policy; GSList *events; MonoJitInfo *ji, *catch_ji; EventInfo ei; - DebuggerTlsData *tls = NULL; - - if (thread_to_tls != NULL) { - MonoInternalThread *thread = mono_thread_internal_current (); - - mono_loader_lock (); - tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, thread); - mono_loader_unlock (); - - if (tls && tls->abort_requested) + GET_TLS_DATA_FROM_THREAD (mono_thread_internal_current ()); + if (tls != NULL) { + if (tls->abort_requested) return; - if (tls && tls->disable_breakpoints) + if (tls->disable_breakpoints) return; } @@ -4965,7 +4989,10 @@ buffer_add_info_for_null_value (Buffer* buf, MonoType* t, MonoDomain* domain) buffer_add_int (buf, m_class_get_rank (mono_class_from_mono_type_internal (t))); if (m_class_get_byval_arg (m_class_get_element_class (mono_class_from_mono_type_internal (t)))->type == MONO_TYPE_CLASS) buffer_add_typeid (buf, domain, m_class_get_element_class (mono_class_from_mono_type_internal (t))); + buffer_add_typeid (buf, domain, mono_class_from_mono_type_internal (t)); break; + default: + buffer_add_typeid (buf, domain, mono_class_from_mono_type_internal (t)); } } /* @@ -5149,6 +5176,9 @@ buffer_add_value_full (Buffer *buf, MonoType *t, void *addr, MonoDomain *domain, buffer_add_byte (buf, MONO_TYPE_VALUETYPE); buffer_add_byte (buf, m_class_is_enumtype (klass)); + + if (CHECK_PROTOCOL_VERSION(2, 61)) + buffer_add_byte(buf, boxed_vtype); buffer_add_typeid (buf, domain, klass); nfields = 0; @@ -5222,9 +5252,8 @@ decode_vtype (MonoType *t, MonoDomain *domain, gpointer void_addr, gpointer void ErrorCode err; is_enum = decode_byte (buf, &buf, limit); - /* Enums are sent as a normal vtype */ - if (is_enum) - return ERR_NOT_IMPLEMENTED; + if (CHECK_PROTOCOL_VERSION(2, 61)) + decode_byte (buf, &buf, limit); klass = decode_typeid (buf, &buf, limit, &d, &err); if (err != ERR_NONE) return err; @@ -5413,7 +5442,7 @@ decode_value_internal (MonoType *t, int type, MonoDomain *domain, guint8 *addr, handle_ref: default: if (MONO_TYPE_IS_REFERENCE (t)) { - if (type == MONO_TYPE_OBJECT || type == MONO_TYPE_STRING) { + if (type == MONO_TYPE_CLASS || type == MONO_TYPE_OBJECT || type == MONO_TYPE_STRING) { int objid = decode_objid (buf, &buf, limit); ErrorCode err; MonoObject *obj; @@ -5435,7 +5464,12 @@ decode_value_internal (MonoType *t, int type, MonoDomain *domain, guint8 *addr, mono_gc_wbarrier_generic_store_internal (addr, obj); } else if (type == VALUE_TYPE_ID_NULL) { + if (CHECK_PROTOCOL_VERSION (2, 59)) { + decode_byte (buf, &buf, limit); + decode_int (buf, &buf, limit); //not used + } *(MonoObject**)addr = NULL; + } else if (type == MONO_TYPE_VALUETYPE) { ERROR_DECL (error); guint8 *buf2; @@ -5452,8 +5486,7 @@ decode_value_internal (MonoType *t, int type, MonoDomain *domain, guint8 *addr, */ buf2 = buf; is_enum = decode_byte (buf, &buf, limit); - if (is_enum) - return ERR_NOT_IMPLEMENTED; + decode_byte (buf, &buf, limit); //ignore is boxed klass = decode_typeid (buf, &buf, limit, &d, &err); if (err != ERR_NONE) return err; @@ -5913,8 +5946,8 @@ add_thread (gpointer key, gpointer value, gpointer user_data) } -static ErrorCode -do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke, guint8 *p, guint8 **endp) +ErrorCode +mono_do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke, guint8 *p, guint8 **endp) { ERROR_DECL (error); guint8 *end = invoke->endp; @@ -5981,7 +6014,7 @@ do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke, guint8 return err; } } else { - if (!(m->flags & METHOD_ATTRIBUTE_STATIC && CHECK_PROTOCOL_VERSION (2, 59))) { //on icordbg I couldn't find an object when invoking a static method maybe I can change this later + if (!(m->flags & METHOD_ATTRIBUTE_STATIC) || (m->flags & METHOD_ATTRIBUTE_STATIC && !CHECK_PROTOCOL_VERSION (2, 59))) { //on icordbg I couldn't find an object when invoking a static method maybe I can change this later err = decode_value(m_class_get_byval_arg(m->klass), domain, this_buf, p, &p, end, FALSE); if (err != ERR_NONE) return err; @@ -6236,7 +6269,7 @@ invoke_method (void) if (err) { /* Fail the other invokes as well */ } else { - err = do_invoke_method (tls, &buf, invoke, p, &p); + err = mono_do_invoke_method (tls, &buf, invoke, p, &p); } if (tls->abort_requested) { @@ -6934,6 +6967,39 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) buffer_add_byte_array (buf, memory, size); break; } + case MDBGPROT_CMD_GET_ASSEMBLY_BY_NAME: { + int i; + char* assembly_name = decode_string (p, &p, end); + //we get 'foo.dll' but mono_assembly_load expects 'foo' so we strip the last dot + char *lookup_name = g_strdup (assembly_name); + for (i = strlen (lookup_name) - 1; i >= 0; --i) { + if (lookup_name [i] == '.') { + lookup_name [i] = 0; + break; + } + } + + //resolve the assembly + MonoImageOpenStatus status; + MonoAssemblyName* aname = mono_assembly_name_new (lookup_name); + if (!aname) { + PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); + buffer_add_int(buf, -1); + break; + } + MonoAssemblyByNameRequest byname_req; + mono_assembly_request_prepare_byname (&byname_req, MONO_ASMCTX_DEFAULT, mono_alc_get_default ()); + MonoAssembly *assembly = mono_assembly_request_byname (aname, &byname_req, &status); + g_free (lookup_name); + mono_assembly_name_free_internal (aname); + if (!assembly) { + PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); + buffer_add_int(buf, -1); + break; + } + buffer_add_assemblyid (buf, mono_get_root_domain (), assembly); + break; + } default: return ERR_NOT_IMPLEMENTED; } @@ -7096,10 +7162,9 @@ event_commands (int command, guint8 *p, guint8 *end, Buffer *buf) g_free (req); return err; } + + GET_TLS_DATA_FROM_THREAD (THREAD_TO_INTERNAL(step_thread)); - mono_loader_lock (); - DebuggerTlsData *tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, THREAD_TO_INTERNAL(step_thread)); - mono_loader_unlock (); g_assert (tls); if (tls->terminated) { @@ -7113,6 +7178,22 @@ event_commands (int command, guint8 *p, guint8 *end, Buffer *buf) g_free (req); return err; } +#ifdef TARGET_WASM + int isBPOnManagedCode = 0; + SingleStepReq *ss_req = req->info; + if (ss_req && ss_req->bps) { + GSList *l; + + for (l = ss_req->bps; l; l = l->next) { + if (((MonoBreakpoint *)l->data)->method->wrapper_type != MONO_WRAPPER_RUNTIME_INVOKE) + isBPOnManagedCode = 1; + } + } + if (!isBPOnManagedCode) { + mono_de_cancel_all_ss (); + } + buffer_add_byte (buf, isBPOnManagedCode); +#endif } else if (req->event_kind == EVENT_KIND_METHOD_ENTRY) { req->info = mono_de_set_breakpoint (NULL, METHOD_ENTRY_IL_OFFSET, req, NULL); } else if (req->event_kind == EVENT_KIND_METHOD_EXIT) { @@ -7743,7 +7824,11 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint buffer_add_string (buf, m_class_get_name_space (klass)); buffer_add_string (buf, m_class_get_name (klass)); // FIXME: byref - name = mono_type_get_name_full (m_class_get_byval_arg (klass), MONO_TYPE_NAME_FORMAT_FULL_NAME); + + MonoTypeNameFormat format = MONO_TYPE_NAME_FORMAT_FULL_NAME; + if (CHECK_PROTOCOL_VERSION(2, 61)) + format = (MonoTypeNameFormat) decode_int (p, &p, end); + name = mono_type_get_name_full (m_class_get_byval_arg (klass), format); buffer_add_string (buf, name); g_free (name); buffer_add_assemblyid (buf, domain, m_class_get_image (klass)->assembly); @@ -8187,6 +8272,23 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint buffer_add_int (buf, value_size); break; } + case MDBGPROT_CMD_TYPE_GET_PARENTS: { + MonoClass *parent_klass = m_class_get_parent (klass); + int count = 0; + while (parent_klass != NULL) + { + count++; + parent_klass = m_class_get_parent (parent_klass); + } + buffer_add_int (buf, count); + parent_klass = m_class_get_parent (klass); + while (parent_klass != NULL) + { + buffer_add_typeid (buf, domain, parent_klass); + parent_klass = m_class_get_parent (parent_klass); + } + break; + } default: err = ERR_NOT_IMPLEMENTED; goto exit; @@ -8242,7 +8344,11 @@ method_commands_internal (int command, MonoMethod *method, MonoDomain *domain, g switch (command) { case CMD_METHOD_GET_NAME: { buffer_add_string (buf, method->name); - break; + break; + } + case MDBGPROT_CMD_METHOD_GET_NAME_FULL: { + buffer_add_string (buf, mono_method_full_name (method, FALSE)); + break; } case MDBGPROT_CMD_METHOD_GET_CLASS_TOKEN: { buffer_add_int (buf, m_class_get_type_token (method->klass)); @@ -8681,6 +8787,16 @@ method_commands_internal (int command, MonoMethod *method, MonoDomain *domain, g buffer_add_assemblyid(buf, mono_domain_get (), m_class_get_image(method->klass)->assembly); break; } + case MDBGPROT_CMD_METHOD_HAS_ASYNC_DEBUG_INFO: { + MonoDebugMethodAsyncInfo* async_method = mono_debug_lookup_method_async_debug_info (method); + if (async_method) { + buffer_add_byte(buf, TRUE); + mono_debug_free_method_async_debug_info (async_method); + } + else + buffer_add_byte(buf, FALSE); + break; + } default: return ERR_NOT_IMPLEMENTED; } @@ -8766,7 +8882,6 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf) break; } case MDBGPROT_CMD_THREAD_GET_CONTEXT: { - DebuggerTlsData *tls; int start_frame; while (!is_suspended ()) { if (suspend_count) @@ -8774,9 +8889,7 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf) } start_frame = decode_int (p, &p, end); - mono_loader_lock (); - tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, thread); - mono_loader_unlock (); + GET_TLS_DATA_FROM_THREAD (thread); if (tls == NULL) return ERR_UNLOADED; @@ -8789,7 +8902,6 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf) break; } case CMD_THREAD_GET_FRAME_INFO: { - DebuggerTlsData *tls; int i, start_frame, length; // Wait for suspending if it already started @@ -8810,10 +8922,7 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf) if (start_frame != 0 || length != -1) return ERR_NOT_IMPLEMENTED; - - mono_loader_lock (); - tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, thread); - mono_loader_unlock (); + GET_TLS_DATA_FROM_THREAD (thread); if (tls == NULL) return ERR_UNLOADED; @@ -8985,7 +9094,6 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) MonoThread *thread_obj; MonoInternalThread *thread; int pos, i, len, frame_idx; - DebuggerTlsData *tls; StackFrame *frame; MonoDebugMethodJitInfo *jit; MonoMethodSignature *sig; @@ -9002,9 +9110,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) id = decode_id (p, &p, end); - mono_loader_lock (); - tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, thread); - mono_loader_unlock (); + GET_TLS_DATA_FROM_THREAD (thread); g_assert (tls); for (i = 0; i < tls->frame_count; ++i) { @@ -9015,7 +9121,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) return ERR_INVALID_FRAMEID; /* The thread is still running native code, can't get frame variables info */ - if (!tls->really_suspended && !tls->async_state.valid) + if (!tls->really_suspended && !tls->async_state.valid) return ERR_NOT_SUSPENDED; frame_idx = i; frame = tls->frames [frame_idx]; @@ -9579,6 +9685,17 @@ object_commands (int command, guint8 *p, guint8 *end, Buffer *buf) buffer_add_typeid (buf, obj->vtable->domain, mono_class_from_mono_type_internal (((MonoReflectionType*)obj->vtable->type)->type)); buffer_add_domainid (buf, obj->vtable->domain); break; + case MDBGPROT_CMD_OBJECT_REF_DELEGATE_GET_METHOD: + buffer_add_methodid (buf, obj->vtable->domain, ((MonoDelegate *)obj)->method); + break; + case MDBGPROT_CMD_OBJECT_IS_DELEGATE: { + MonoType *type = m_class_get_byval_arg (obj_type); + if (m_class_is_delegate (obj_type) || (type->type == MONO_TYPE_GENERICINST && m_class_is_delegate (type->data.generic_class->container_class))) + buffer_add_byte (buf, TRUE); + else + buffer_add_byte (buf, FALSE); + break; + } default: err = ERR_NOT_IMPLEMENTED; goto exit; @@ -9895,6 +10012,62 @@ wait_for_attach (void) return TRUE; } +ErrorCode +mono_process_dbg_packet (int id, CommandSet command_set, int command, gboolean *no_reply, guint8 *buf, guint8 *end, Buffer *ret_buf) +{ + ErrorCode err; + /* Process the request */ + switch (command_set) { + case CMD_SET_VM: + err = vm_commands (command, id, buf, end, ret_buf); + if (err == ERR_NONE && command == CMD_VM_INVOKE_METHOD) + /* Sent after the invoke is complete */ + *no_reply = TRUE; + break; + case CMD_SET_EVENT_REQUEST: + err = event_commands (command, buf, end, ret_buf); + break; + case CMD_SET_APPDOMAIN: + err = domain_commands (command, buf, end, ret_buf); + break; + case CMD_SET_ASSEMBLY: + err = assembly_commands (command, buf, end, ret_buf); + break; + case CMD_SET_MODULE: + err = module_commands (command, buf, end, ret_buf); + break; + case CMD_SET_FIELD: + err = field_commands (command, buf, end, ret_buf); + break; + case CMD_SET_TYPE: + err = type_commands (command, buf, end, ret_buf); + break; + case CMD_SET_METHOD: + err = method_commands (command, buf, end, ret_buf); + break; + case CMD_SET_THREAD: + err = thread_commands (command, buf, end, ret_buf); + break; + case CMD_SET_STACK_FRAME: + err = frame_commands (command, buf, end, ret_buf); + break; + case CMD_SET_ARRAY_REF: + err = array_commands (command, buf, end, ret_buf); + break; + case CMD_SET_STRING_REF: + err = string_commands (command, buf, end, ret_buf); + break; + case CMD_SET_POINTER: + err = pointer_commands (command, buf, end, ret_buf); + break; + case CMD_SET_OBJECT_REF: + err = object_commands (command, buf, end, ret_buf); + break; + default: + err = ERR_NOT_IMPLEMENTED; + } + return err; +} /* * debugger_thread: * @@ -9990,57 +10163,7 @@ debugger_thread (void *arg) err = ERR_NONE; no_reply = FALSE; - - /* Process the request */ - switch (command_set) { - case CMD_SET_VM: - err = vm_commands (command, id, p, end, &buf); - if (err == ERR_NONE && command == CMD_VM_INVOKE_METHOD) - /* Sent after the invoke is complete */ - no_reply = TRUE; - break; - case CMD_SET_EVENT_REQUEST: - err = event_commands (command, p, end, &buf); - break; - case CMD_SET_APPDOMAIN: - err = domain_commands (command, p, end, &buf); - break; - case CMD_SET_ASSEMBLY: - err = assembly_commands (command, p, end, &buf); - break; - case CMD_SET_MODULE: - err = module_commands (command, p, end, &buf); - break; - case CMD_SET_FIELD: - err = field_commands (command, p, end, &buf); - break; - case CMD_SET_TYPE: - err = type_commands (command, p, end, &buf); - break; - case CMD_SET_METHOD: - err = method_commands (command, p, end, &buf); - break; - case CMD_SET_THREAD: - err = thread_commands (command, p, end, &buf); - break; - case CMD_SET_STACK_FRAME: - err = frame_commands (command, p, end, &buf); - break; - case CMD_SET_ARRAY_REF: - err = array_commands (command, p, end, &buf); - break; - case CMD_SET_STRING_REF: - err = string_commands (command, p, end, &buf); - break; - case CMD_SET_POINTER: - err = pointer_commands (command, p, end, &buf); - break; - case CMD_SET_OBJECT_REF: - err = object_commands (command, p, end, &buf); - break; - default: - err = ERR_NOT_IMPLEMENTED; - } + err = mono_process_dbg_packet (id, command_set, command, &no_reply, p, end, &buf); if (command_set == CMD_SET_VM && command == CMD_VM_START_BUFFERING) { buffer_replies = TRUE; @@ -10111,10 +10234,10 @@ mono_debugger_agent_init (void) cbs.breakpoint_from_context = debugger_agent_breakpoint_from_context; cbs.free_mem_manager = debugger_agent_free_mem_manager; cbs.unhandled_exception = debugger_agent_unhandled_exception; - cbs.handle_exception = debugger_agent_handle_exception; + cbs.handle_exception = mono_debugger_agent_handle_exception; cbs.begin_exception_filter = debugger_agent_begin_exception_filter; cbs.end_exception_filter = debugger_agent_end_exception_filter; - cbs.user_break = debugger_agent_user_break; + cbs.user_break = mono_dbg_debugger_agent_user_break; cbs.debug_log = debugger_agent_debug_log; cbs.debug_log_is_enabled = debugger_agent_debug_log_is_enabled; cbs.send_crash = mono_debugger_agent_send_crash; diff --git a/src/mono/mono/mini/debugger-agent.h b/src/mono/mono/mini/debugger-agent.h index 500e8e610c3b54..bf0a06e2056fef 100644 --- a/src/mono/mono/mini/debugger-agent.h +++ b/src/mono/mono/mini/debugger-agent.h @@ -6,12 +6,45 @@ #define __MONO_DEBUGGER_AGENT_H__ #include "mini.h" +#include "debugger-protocol.h" + #include #define MONO_DBG_CALLBACKS_VERSION (4) // 2. debug_log parameters changed from MonoString* to MonoStringHandle // 3. debug_log parameters changed from MonoStringHandle back to MonoString* +typedef struct _InvokeData InvokeData; + +struct _InvokeData +{ + int id; + int flags; + guint8 *p; + guint8 *endp; + /* This is the context which needs to be restored after the invoke */ + MonoContext ctx; + gboolean has_ctx; + /* + * If this is set, invoke this method with the arguments given by ARGS. + */ + MonoMethod *method; + gpointer *args; + guint32 suspend_count; + int nmethods; + + InvokeData *last_invoke; +}; + +typedef struct { + const char *name; + void (*connect) (const char *address); + void (*close1) (void); + void (*close2) (void); + gboolean (*send) (void *buf, int len); + int (*recv) (void *buf, int len); +} DebuggerTransport; + struct _MonoDebuggerCallbacks { int version; void (*parse_options) (char *options); @@ -46,4 +79,37 @@ mono_debugger_agent_stub_init (void); MONO_API MONO_RT_EXTERNAL_ONLY gboolean mono_debugger_agent_transport_handshake (void); +MONO_API void +mono_debugger_agent_register_transport (DebuggerTransport *trans); + +MdbgProtErrorCode +mono_process_dbg_packet (int id, MdbgProtCommandSet command_set, int command, gboolean *no_reply, guint8 *p, guint8 *end, MdbgProtBuffer *buf); + +void +mono_init_debugger_agent_for_wasm (int log_level); + +void* +mono_dbg_create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, MdbgProtEventKind kind); + +void +mono_dbg_process_breakpoint_events (void *_evts, MonoMethod *method, MonoContext *ctx, int il_offset); + +void +mono_dbg_debugger_agent_user_break (void); + +void +mono_wasm_save_thread_context (void); + +DebuggerTlsData* +mono_wasm_get_tls (void); + +MdbgProtErrorCode +mono_do_invoke_method (DebuggerTlsData *tls, MdbgProtBuffer *buf, InvokeData *invoke, guint8 *p, guint8 **endp); + +void +mono_debugger_agent_handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame); + +void +mono_ss_discard_frame_context (void *the_tls); + #endif diff --git a/src/mono/mono/mini/debugger-engine.c b/src/mono/mono/mini/debugger-engine.c index 2008b51338ac85..38ac2d081f594a 100644 --- a/src/mono/mono/mini/debugger-engine.c +++ b/src/mono/mono/mini/debugger-engine.c @@ -388,13 +388,6 @@ collect_domain_bp (gpointer key, gpointer value, gpointer user_data) jit_mm_unlock (jit_mm); } -void -mono_de_clear_all_breakpoints (void) -{ - while (breakpoints->len) - mono_de_clear_breakpoint ((MonoBreakpoint*)g_ptr_array_index (breakpoints, 0)); -} - /* * mono_de_set_breakpoint: * diff --git a/src/mono/mono/mini/debugger-engine.h b/src/mono/mono/mini/debugger-engine.h index 24ad575093e696..20fe88fedb8d76 100644 --- a/src/mono/mono/mini/debugger-engine.h +++ b/src/mono/mono/mini/debugger-engine.h @@ -497,7 +497,6 @@ MonoBreakpoint* mono_de_set_breakpoint (MonoMethod *method, long il_offset, Even void mono_de_collect_breakpoints_by_sp (SeqPoint *sp, MonoJitInfo *ji, GPtrArray *ss_reqs, GPtrArray *bp_reqs); void mono_de_clear_breakpoints_for_domain (MonoDomain *domain); void mono_de_add_pending_breakpoints (MonoMethod *method, MonoJitInfo *ji); -void mono_de_clear_all_breakpoints (void); MonoBreakpoint * mono_de_get_breakpoint_by_id (int id); //single stepping @@ -545,3 +544,15 @@ void win32_debugger_log(FILE *stream, const gchar *format, ...); #define PRINT_ERROR_MSG(...) g_printerr (__VA_ARGS__) #define PRINT_MSG(...) g_print (__VA_ARGS__) #endif + +int +mono_ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *args); + +void +mono_ss_args_destroy (SingleStepArgs *ss_args); + +int +mono_get_this_async_id (DbgEngineStackFrame *frame); + +void +mono_ss_calculate_framecount (void *tls, MonoContext *ctx, gboolean force_use_ctx, DbgEngineStackFrame ***frames, int *nframes); diff --git a/src/mono/mono/mini/debugger-protocol.c b/src/mono/mono/mini/debugger-protocol.c index 1402dae3d36efe..2fe3096608a511 100644 --- a/src/mono/mono/mini/debugger-protocol.c +++ b/src/mono/mono/mini/debugger-protocol.c @@ -56,7 +56,6 @@ m_dbgprot_decode_int (uint8_t *buf, uint8_t **endbuf, uint8_t *limit) { *endbuf = buf + 4; g_assert (*endbuf <= limit); - return (((int)buf [0]) << 24) | (((int)buf [1]) << 16) | (((int)buf [2]) << 8) | (((int)buf [3]) << 0); } diff --git a/src/mono/mono/mini/debugger-protocol.h b/src/mono/mono/mini/debugger-protocol.h index 87d9e232c851af..24e8a2e42c27e6 100644 --- a/src/mono/mono/mini/debugger-protocol.h +++ b/src/mono/mono/mini/debugger-protocol.h @@ -34,7 +34,8 @@ typedef enum { MDBGPROT_CMD_VM_START_BUFFERING = 14, MDBGPROT_CMD_VM_STOP_BUFFERING = 15, MDBGPROT_CMD_VM_READ_MEMORY = 16, - MDBGPROT_CMD_VM_WRITE_MEMORY = 17 + MDBGPROT_CMD_VM_WRITE_MEMORY = 17, + MDBGPROT_CMD_GET_ASSEMBLY_BY_NAME = 18 } MdbgProtCmdVM; typedef enum { @@ -173,7 +174,9 @@ typedef enum { MDBGPROT_CMD_METHOD_MAKE_GENERIC_METHOD = 10, MDBGPROT_CMD_METHOD_TOKEN = 11, MDBGPROT_CMD_METHOD_ASSEMBLY = 12, - MDBGPROT_CMD_METHOD_GET_CLASS_TOKEN = 13 + MDBGPROT_CMD_METHOD_GET_CLASS_TOKEN = 13, + MDBGPROT_CMD_METHOD_HAS_ASYNC_DEBUG_INFO = 14, + MDBGPROT_CMD_METHOD_GET_NAME_FULL = 15 } MdbgProtCmdMethod; typedef enum { @@ -197,7 +200,8 @@ typedef enum { MDBGPROT_CMD_TYPE_IS_INITIALIZED = 18, MDBGPROT_CMD_TYPE_CREATE_INSTANCE = 19, MDBGPROT_CMD_TYPE_GET_VALUE_SIZE = 20, - MDBGPROT_CMD_TYPE_GET_VALUES_ICORDBG = 21 + MDBGPROT_CMD_TYPE_GET_VALUES_ICORDBG = 21, + MDBGPROT_CMD_TYPE_GET_PARENTS = 22 } MdbgProtCmdType; typedef enum { @@ -235,7 +239,9 @@ typedef enum { MDBGPROT_CMD_OBJECT_REF_GET_DOMAIN = 5, MDBGPROT_CMD_OBJECT_REF_SET_VALUES = 6, MDBGPROT_CMD_OBJECT_REF_GET_INFO = 7, - MDBGPROT_CMD_OBJECT_REF_GET_VALUES_ICORDBG = 8 + MDBGPROT_CMD_OBJECT_REF_GET_VALUES_ICORDBG = 8, + MDBGPROT_CMD_OBJECT_REF_DELEGATE_GET_METHOD = 9, + MDBGPROT_CMD_OBJECT_IS_DELEGATE = 10 } MdbgProtCmdObject; typedef enum { diff --git a/src/mono/mono/mini/mini-wasm-debugger.c b/src/mono/mono/mini/mini-wasm-debugger.c index 352bb81deb9232..fd19c6a846277b 100644 --- a/src/mono/mono/mini/mini-wasm-debugger.c +++ b/src/mono/mono/mini/mini-wasm-debugger.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include //XXX This is dirty, extend ee.h to support extracting info from MonoInterpFrameHandle #include @@ -24,84 +26,29 @@ static int log_level = 1; -enum { - EXCEPTION_MODE_NONE, - EXCEPTION_MODE_UNCAUGHT, - EXCEPTION_MODE_ALL -}; - -// Flags for get_*_properties -#define GPFLAG_NONE 0x0000 -#define GPFLAG_OWN_PROPERTIES 0x0001 -#define GPFLAG_ACCESSORS_ONLY 0x0002 -#define GPFLAG_EXPAND_VALUETYPES 0x0004 - //functions exported to be used by JS G_BEGIN_DECLS -EMSCRIPTEN_KEEPALIVE int mono_wasm_set_breakpoint (const char *assembly_name, int method_token, int il_offset); -EMSCRIPTEN_KEEPALIVE int mono_wasm_remove_breakpoint (int bp_id); -EMSCRIPTEN_KEEPALIVE int mono_wasm_current_bp_id (void); -EMSCRIPTEN_KEEPALIVE void mono_wasm_enum_frames (void); -EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_local_vars (int scope, int* pos, int len); -EMSCRIPTEN_KEEPALIVE void mono_wasm_clear_all_breakpoints (void); -EMSCRIPTEN_KEEPALIVE int mono_wasm_setup_single_step (int kind); -EMSCRIPTEN_KEEPALIVE int mono_wasm_pause_on_exceptions (int state); -EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_object_properties (int object_id, int gpflags); -EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_array_values (int object_id, int start_idx, int count, int gpflags); -EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_object (int object_id, const char* name); -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_on_frame (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); +EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, guint8* data, unsigned int size); +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); + //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); -extern void mono_wasm_fire_bp (void); -extern void mono_wasm_fire_exception (int exception_obj_id, const char* message, const char* class_name, gboolean uncaught); -extern void mono_wasm_add_obj_var (const char*, const char*, guint64); -extern void mono_wasm_add_enum_var (const char*, const char*, guint64); -extern void mono_wasm_add_func_var (const char*, const char*, guint64); -extern void mono_wasm_add_properties_var (const char*, gint32); -extern void mono_wasm_add_array_item (int); -extern void mono_wasm_set_is_async_method (guint64); -extern void mono_wasm_add_typed_value (const char *type, const char *str_value, double value); +extern void mono_wasm_fire_debugger_agent_message (void); extern void mono_wasm_asm_loaded (const char *asm_name, const char *assembly_data, guint32 assembly_len, const char *pdb_data, guint32 pdb_len); G_END_DECLS -static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags); static void handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame); +static gboolean receive_debugger_agent_message (void *data, int len); static void assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly); -static MonoObject* mono_runtime_try_invoke_internal (MonoMethod *method, void *obj, void **params, MonoObject **exc, MonoError* error); //FIXME move all of those fields to the profiler object static gboolean debugger_enabled; static gboolean has_pending_lazy_loaded_assemblies; -static int event_request_id; -static GHashTable *objrefs; -static GHashTable *obj_to_objref; -static int objref_id = 0; -static int pause_on_exc = EXCEPTION_MODE_NONE; -static MonoObject* exception_on_runtime_invoke = NULL; - -static const char* -all_getters_allowed_class_names[] = { - "System.DateTime", - "System.DateTimeOffset", - "System.TimeSpan" -}; - -static const char* -to_string_as_descr_names[] = { - "System.DateTime", - "System.DateTimeOffset", - "System.Decimal", - "System.TimeSpan" -}; #define THREAD_TO_INTERNAL(thread) (thread)->internal_thread @@ -129,14 +76,6 @@ void wasm_debugger_log (int level, const gchar *format, ...) g_free (mesg); } -static void -inplace_tolower (char *c) -{ - int i; - for (i = strlen (c) - 1; i >= 0; --i) - c [i] = tolower (c [i]); -} - static void jit_done (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo) { @@ -149,78 +88,6 @@ appdomain_load (MonoProfiler *prof, MonoDomain *domain) mono_de_domain_add (domain); } -/* Frame state handling */ -static GPtrArray *frames; - -static void -free_frame (DbgEngineStackFrame *frame) -{ - g_free (frame); -} - -static gboolean -collect_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data) -{ - SeqPoint sp; - MonoMethod *method; - - //skip wrappers - if (info->type != FRAME_TYPE_MANAGED && info->type != FRAME_TYPE_INTERP) - return FALSE; - - if (info->ji) - method = jinfo_get_method (info->ji); - else - method = info->method; - - if (!method) - return FALSE; - - PRINT_DEBUG_MSG (2, "collect_frames: Reporting method %s native_offset %d, wrapper_type: %d\n", method->name, info->native_offset, method->wrapper_type); - - if (!mono_find_prev_seq_point_for_native_offset (method, info->native_offset, NULL, &sp)) - PRINT_DEBUG_MSG (2, "collect_frames: Failed to lookup sequence point. method: %s, native_offset: %d \n", method->name, info->native_offset); - - - StackFrame *frame = g_new0 (StackFrame, 1); - frame->de.ji = info->ji; - frame->de.domain = mono_get_root_domain (); - frame->de.method = method; - frame->de.native_offset = info->native_offset; - - frame->il_offset = info->il_offset; - frame->interp_frame = info->interp_frame; - frame->frame_addr = info->frame_addr; - - g_ptr_array_add (frames, frame); - - return FALSE; -} - -static void -free_frame_state (void) -{ - if (frames) { - int i; - for (i = 0; i < frames->len; ++i) - free_frame ((DbgEngineStackFrame*)g_ptr_array_index (frames, i)); - g_ptr_array_set_size (frames, 0); - } -} - -static void -compute_frames (void) { - if (frames) { - int i; - for (i = 0; i < frames->len; ++i) - free_frame ((DbgEngineStackFrame*)g_ptr_array_index (frames, i)); - g_ptr_array_set_size (frames, 0); - } else { - frames = g_ptr_array_new (); - } - - mono_walk_stack_with_ctx (collect_frames, NULL, MONO_UNWIND_NONE, NULL); -} static MonoContext* tls_get_restore_state (void *tls) { @@ -247,17 +114,14 @@ begin_single_step_processing (MonoContext *ctx, gboolean from_signal) static void ss_discard_frame_context (void *the_tls) { - free_frame_state (); + mono_ss_discard_frame_context (mono_wasm_get_tls()); } static void ss_calculate_framecount (void *tls, MonoContext *ctx, gboolean force_use_ctx, DbgEngineStackFrame ***out_frames, int *nframes) { - compute_frames (); - if (out_frames) - *out_frames = (DbgEngineStackFrame **)frames->pdata; - if (nframes) - *nframes = frames->len; + mono_wasm_save_thread_context(); + mono_ss_calculate_framecount(mono_wasm_get_tls(), NULL, force_use_ctx, out_frames, nframes); } static gboolean @@ -272,132 +136,8 @@ ensure_runtime_is_suspended (void) return DE_ERR_NONE; } -static int -get_object_id (MonoObject *obj) -{ - ObjRef *ref; - if (!obj) - return 0; - - ref = (ObjRef *)g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj))); - if (ref) - return ref->id; - ref = g_new0 (ObjRef, 1); - ref->id = mono_atomic_inc_i32 (&objref_id); - ref->handle = mono_gchandle_new_weakref_internal (obj, FALSE); - g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref); - g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref); - return ref->id; -} - - -static int -get_this_async_id (DbgEngineStackFrame *frame) -{ - MonoClassField *builder_field; - gpointer builder; - MonoMethod *method; - MonoObject *ex; - ERROR_DECL (error); - MonoObject *obj; - - /* - * FRAME points to a method in a state machine class/struct. - * Call the ObjectIdForDebugger method of the associated method builder type. - */ - builder = get_async_method_builder (frame); - if (!builder) - return 0; - - builder_field = mono_class_get_field_from_name_full (get_class_to_get_builder_field(frame), "<>t__builder", NULL); - if (!builder_field) - return 0; - - method = get_object_id_for_debugger_method (mono_class_from_mono_type_internal (builder_field->type)); - if (!method) { - return 0; - } - - obj = mono_runtime_try_invoke_internal (method, builder, NULL, &ex, error); - mono_error_assert_ok (error); - - return get_object_id (obj); -} - -typedef struct { - gboolean is_ss; //do I need this? -} BpEvents; - -static void* -create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, EventKind kind) -{ - PRINT_DEBUG_MSG (1, "ss_reqs %d bp_reqs %d\n", ss_reqs->len, bp_reqs->len); - if ((ss_reqs && ss_reqs->len) || (bp_reqs && bp_reqs->len)) { - BpEvents *evts = g_new0 (BpEvents, 1); //just a non-null value to make sure we can raise it on process_breakpoint_events - evts->is_ss = (ss_reqs && ss_reqs->len); - return evts; - } - return NULL; -} - -static void -process_breakpoint_events (void *_evts, MonoMethod *method, MonoContext *ctx, int il_offsets) -{ - BpEvents *evts = (BpEvents*)_evts; - if (evts) { - if (evts->is_ss) - mono_de_cancel_all_ss (); - mono_wasm_fire_bp (); - g_free (evts); - } -} - -static void -no_seq_points_found (MonoMethod *method, int offset) -{ - /* - * This can happen in full-aot mode with assemblies AOTed without the 'soft-debug' option to save space. - */ - PRINT_DEBUG_MSG (1, "Unable to find seq points for method '%s', offset 0x%x.\n", mono_method_full_name (method, TRUE), offset); -} - #define DBG_NOT_SUSPENDED 1 -static int -ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *ss_args) -{ - PRINT_DEBUG_MSG (1, "ss_create_init_args\n"); - int dummy = 0; - ss_req->start_sp = ss_req->last_sp = &dummy; - compute_frames (); - memset (ss_args, 0, sizeof (*ss_args)); - - // This shouldn't happen - maybe should assert here ? - if (frames->len == 0) { - PRINT_DEBUG_MSG (1, "SINGLE STEPPING FOUND NO FRAMES"); - return DBG_NOT_SUSPENDED; - } - - DbgEngineStackFrame *frame = (DbgEngineStackFrame*)g_ptr_array_index (frames, 0); - ss_req->start_method = ss_args->method = frame->method; - gboolean found_sp = mono_find_prev_seq_point_for_native_offset (frame->method, frame->native_offset, &ss_args->info, &ss_args->sp); - if (!found_sp) - no_seq_points_found (frame->method, frame->native_offset); - g_assert (found_sp); - - ss_args->frames = (DbgEngineStackFrame**)frames->pdata; - ss_args->nframes = frames->len; - //XXX do sp - - return DE_ERR_NONE; -} - -static void -ss_args_destroy (SingleStepArgs *ss_args) -{ - //nothing to do -} - static int handle_multiple_ss_requests (void) { mono_de_cancel_all_ss (); @@ -419,13 +159,13 @@ mono_wasm_debugger_init (void) .ss_calculate_framecount = ss_calculate_framecount, .ensure_jit = ensure_jit, .ensure_runtime_is_suspended = ensure_runtime_is_suspended, - .get_this_async_id = get_this_async_id, + .get_this_async_id = mono_get_this_async_id, .set_set_notification_for_wait_completion_flag = set_set_notification_for_wait_completion_flag, .get_notify_debugger_of_wait_completion_method = get_notify_debugger_of_wait_completion_method, - .create_breakpoint_events = create_breakpoint_events, - .process_breakpoint_events = process_breakpoint_events, - .ss_create_init_args = ss_create_init_args, - .ss_args_destroy = ss_args_destroy, + .create_breakpoint_events = mono_dbg_create_breakpoint_events, + .process_breakpoint_events = mono_dbg_process_breakpoint_events, + .ss_create_init_args = mono_ss_create_init_args, + .ss_args_destroy = mono_ss_args_destroy, .handle_multiple_ss_requests = handle_multiple_ss_requests, }; @@ -444,11 +184,16 @@ mono_wasm_debugger_init (void) mono_profiler_set_domain_loaded_callback (prof, appdomain_load); mono_profiler_set_assembly_loaded_callback (prof, assembly_loaded); - obj_to_objref = g_hash_table_new (NULL, NULL); - objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref); + mini_get_dbg_callbacks ()->handle_exception = mono_debugger_agent_handle_exception; + mini_get_dbg_callbacks ()->user_break = mono_dbg_debugger_agent_user_break; + +//debugger-agent initialization + DebuggerTransport trans; + trans.name = "buffer-wasm-communication"; + trans.send = receive_debugger_agent_message; - mini_get_dbg_callbacks ()->handle_exception = handle_exception; - mini_get_dbg_callbacks ()->user_break = mono_wasm_user_break; + mono_debugger_agent_register_transport (&trans); + mono_init_debugger_agent_for_wasm (log_level); } MONO_API void @@ -459,69 +204,6 @@ mono_wasm_enable_debugging (int debug_level) log_level = debug_level; } -EMSCRIPTEN_KEEPALIVE int -mono_wasm_pause_on_exceptions (int state) -{ - pause_on_exc = state; - PRINT_DEBUG_MSG (1, "setting pause on exception: %d\n", pause_on_exc); - return 1; -} - -EMSCRIPTEN_KEEPALIVE int -mono_wasm_setup_single_step (int kind) -{ - int nmodifiers = 1; - - PRINT_DEBUG_MSG (2, ">>>> mono_wasm_setup_single_step %d\n", kind); - EventRequest *req = (EventRequest *)g_malloc0 (sizeof (EventRequest) + (nmodifiers * sizeof (Modifier))); - req->id = ++event_request_id; - req->event_kind = EVENT_KIND_STEP; - // DE doesn't care about suspend_policy - // req->suspend_policy = SUSPEND_POLICY_ALL; - req->nmodifiers = nmodifiers; - - StepSize size = STEP_SIZE_MIN; - - //FIXME I DON'T KNOW WHAT I'M DOING!!!!! filter all the things. - StepFilter filter = (StepFilter)(STEP_FILTER_STATIC_CTOR | STEP_FILTER_DEBUGGER_HIDDEN | STEP_FILTER_DEBUGGER_STEP_THROUGH | STEP_FILTER_DEBUGGER_NON_USER_CODE); - req->modifiers [0].data.filter = filter; - - StepDepth depth; - switch (kind) { - case 0: //into - depth = STEP_DEPTH_INTO; - break; - case 1: //out - depth = STEP_DEPTH_OUT; - break; - case 2: //over - depth = STEP_DEPTH_OVER; - break; - default: - g_error ("[dbg] unknown step kind %d", kind); - } - - DbgEngineErrorCode err = mono_de_ss_create (THREAD_TO_INTERNAL (mono_thread_current ()), size, depth, filter, req); - if (err != DE_ERR_NONE) { - PRINT_DEBUG_MSG (1, "[dbg] Failed to setup single step request"); - } - PRINT_DEBUG_MSG (1, "[dbg] single step is in place, now what?\n"); - SingleStepReq *ss_req = req->info; - int isBPOnNativeCode = 0; - if (ss_req && ss_req->bps) { - GSList *l; - - for (l = ss_req->bps; l; l = l->next) { - if (((MonoBreakpoint *)l->data)->method->wrapper_type != MONO_WRAPPER_RUNTIME_INVOKE) - isBPOnNativeCode = 1; - } - } - if (!isBPOnNativeCode) { - mono_de_cancel_all_ss (); - } - return isBPOnNativeCode; -} - static void assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly) { @@ -551,1403 +233,203 @@ assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly) } } -static void -handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame) -{ - ERROR_DECL (error); - const char *default_error_message = "Failed to get exception message."; - - PRINT_DEBUG_MSG (1, "handle exception - %d - %p - %p - %p\n", pause_on_exc, exc, throw_ctx, catch_ctx); - - //normal mono_runtime_try_invoke does not capture the exception and this is a temporary workaround. - exception_on_runtime_invoke = (MonoObject*)exc; - - if (pause_on_exc == EXCEPTION_MODE_NONE) - return; - if (pause_on_exc == EXCEPTION_MODE_UNCAUGHT && catch_ctx != NULL) - return; - - int obj_id = get_object_id ((MonoObject *)exc); - char *error_message = mono_string_to_utf8_checked_internal (exc->message, error); - const char *class_name = mono_class_full_name (mono_object_class (exc)); - PRINT_DEBUG_MSG (2, "handle exception - calling mono_wasm_fire_exc(): %d - message - %s, class_name: %s\n", obj_id, !is_ok (error) ? error_message : default_error_message, class_name); - - mono_wasm_fire_exception (obj_id, !is_ok (error) ? error_message : default_error_message, class_name, !catch_ctx); - - if (error_message != NULL) - g_free (error_message); - - PRINT_DEBUG_MSG (2, "handle exception - done\n"); +void +mono_wasm_single_step_hit (void) +{ + mono_de_process_single_step (mono_wasm_get_tls(), FALSE); } - -EMSCRIPTEN_KEEPALIVE void -mono_wasm_clear_all_breakpoints (void) +void +mono_wasm_breakpoint_hit (void) { - PRINT_DEBUG_MSG (1, "CLEAR BREAKPOINTS\n"); - mono_de_clear_all_breakpoints (); + mono_de_process_breakpoint (mono_wasm_get_tls(), FALSE); } -EMSCRIPTEN_KEEPALIVE int -mono_wasm_set_breakpoint (const char *assembly_name, int method_token, int il_offset) +static gboolean +write_value_to_buffer (MdbgProtBuffer *buf, MonoTypeEnum type, const char* variableValue) { - int i; - ERROR_DECL (error); - PRINT_DEBUG_MSG (1, "SET BREAKPOINT: assembly %s method %x offset %x\n", assembly_name, method_token, il_offset); - - - //we get 'foo.dll' but mono_assembly_load expects 'foo' so we strip the last dot - char *lookup_name = g_strdup (assembly_name); - for (i = strlen (lookup_name) - 1; i >= 0; --i) { - if (lookup_name [i] == '.') { - lookup_name [i] = 0; + char* endptr; + errno = 0; + buffer_add_byte (buf, type); + switch (type) { + case MONO_TYPE_BOOLEAN: + if (!strcasecmp (variableValue, "True")) + buffer_add_int (buf, 1); + else if (!strcasecmp (variableValue, "False")) + buffer_add_int (buf, 0); + else + return FALSE; + break; + case MONO_TYPE_CHAR: + if (strlen (variableValue) > 1) + return FALSE; + buffer_add_int (buf, (variableValue [0])); + break; + case MONO_TYPE_I1: { + intmax_t val = strtoimax (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + if (val >= -128 && val <= 127) + buffer_add_int (buf, val); + else + return FALSE; break; } + case MONO_TYPE_U1: { + intmax_t val = strtoimax (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + if (val >= 0 && val <= 255) + buffer_add_int (buf, val); + else + return FALSE; + break; + } + case MONO_TYPE_I2: { + intmax_t val = strtoimax (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + if (val >= -32768 && val <= 32767) + buffer_add_int (buf, val); + else + return FALSE; + break; + } + case MONO_TYPE_U2: { + intmax_t val = strtoimax (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + if (val >= 0 && val <= 65535) + buffer_add_int (buf, val); + else + return FALSE; + break; + } + case MONO_TYPE_I4: { + intmax_t val = strtoimax (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + if (val >= -2147483648 && val <= 2147483647) + buffer_add_int (buf, val); + else + return FALSE; + break; + } + case MONO_TYPE_U4: { + intmax_t val = strtoimax (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + if (val >= 0 && val <= 4294967295) + buffer_add_int (buf, val); + else + return FALSE; + break; + } + case MONO_TYPE_I8: { + long long val = strtoll (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + buffer_add_long (buf, val); + break; + } + case MONO_TYPE_U8: { + long long val = strtoll (variableValue, &endptr, 10); + if (errno != 0) + return FALSE; + buffer_add_long (buf, val); + break; + } + case MONO_TYPE_R4: { + gfloat val = strtof (variableValue, &endptr); + if (errno != 0) + return FALSE; + buffer_add_int (buf, *((gint32*)(&val))); + break; + } + case MONO_TYPE_R8: { + gdouble val = strtof (variableValue, &endptr); + if (errno != 0) + return FALSE; + buffer_add_long (buf, *((guint64*)(&val))); + break; + } + default: + return FALSE; } - - //resolve the assembly - MonoImageOpenStatus status; - MonoAssemblyName* aname = mono_assembly_name_new (lookup_name); - MonoAssemblyByNameRequest byname_req; - mono_assembly_request_prepare_byname (&byname_req, MONO_ASMCTX_DEFAULT, mono_alc_get_default ()); - MonoAssembly *assembly = mono_assembly_request_byname (aname, &byname_req, &status); - g_free (lookup_name); - if (!assembly) { - PRINT_DEBUG_MSG (1, "Could not resolve assembly %s\n", assembly_name); - return -1; - } - - mono_assembly_name_free_internal (aname); - - MonoMethod *method = mono_get_method_checked (assembly->image, MONO_TOKEN_METHOD_DEF | method_token, NULL, NULL, error); - if (!method) { - //FIXME don't swallow the error - PRINT_DEBUG_MSG (1, "Could not find method due to %s\n", mono_error_get_message (error)); - mono_error_cleanup (error); - return -1; - } - - //FIXME right now none of the EventRequest fields are used by debugger-engine - EventRequest *req = g_new0 (EventRequest, 1); - req->id = ++event_request_id; - req->event_kind = EVENT_KIND_BREAKPOINT; - //DE doesn't care about suspend_policy - // req->suspend_policy = SUSPEND_POLICY_ALL; - req->nmodifiers = 0; //funny thing, - - // BreakPointRequest *req = breakpoint_request_new (assembly, method, il_offset); - MonoBreakpoint *bp = mono_de_set_breakpoint (method, il_offset, req, error); - - if (!bp) { - PRINT_DEBUG_MSG (1, "Could not set breakpoint to %s\n", mono_error_get_message (error)); - mono_error_cleanup (error); - return 0; - } - - PRINT_DEBUG_MSG (1, "NEW BP %p has id %d\n", req, req->id); - return req->id; -} - -EMSCRIPTEN_KEEPALIVE int -mono_wasm_remove_breakpoint (int bp_id) -{ - MonoBreakpoint *bp = mono_de_get_breakpoint_by_id (bp_id); - if (!bp) - return 0; - - mono_de_clear_breakpoint (bp); - return 1; + return TRUE; } -void -mono_wasm_single_step_hit (void) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_set_is_debugger_attached (gboolean is_attached) { - mono_de_process_single_step (NULL, FALSE); + mono_set_is_debugger_attached (is_attached); + if (is_attached && has_pending_lazy_loaded_assemblies) + { + GPtrArray *assemblies = mono_alc_get_all_loaded_assemblies (); + for (int i = 0; i < assemblies->len; ++i) { + MonoAssembly *ass = (MonoAssembly*)g_ptr_array_index (assemblies, i); + assembly_loaded (NULL, ass); + } + g_ptr_array_free (assemblies, TRUE); + has_pending_lazy_loaded_assemblies = FALSE; + } } -void -mono_wasm_breakpoint_hit (void) +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) { - mono_de_process_breakpoint (NULL, FALSE); - // mono_wasm_fire_bp (); + MdbgProtBuffer bufWithParms; + buffer_init (&bufWithParms, 128); + m_dbgprot_buffer_add_data (&bufWithParms, data, size); + if (!write_value_to_buffer(&bufWithParms, valtype, newvalue)) { + EM_ASM ({ + MONO.mono_wasm_add_dbg_command_received ($0, $1, $2, $3); + }, 0, id, 0, 0); + return TRUE; + } + mono_wasm_send_dbg_command(id, command_set, command, bufWithParms.buf, m_dbgprot_buffer_len(&bufWithParms)); + buffer_free (&bufWithParms); + return TRUE; } -void -mono_wasm_user_break (void) +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, guint8* data, unsigned int size) { - mono_wasm_fire_bp (); + ss_calculate_framecount (NULL, NULL, TRUE, NULL, NULL); + MdbgProtBuffer buf; + buffer_init (&buf, 128); + gboolean no_reply; + MdbgProtErrorCode error = 0; + if (command_set == MDBGPROT_CMD_SET_VM && command == MDBGPROT_CMD_VM_INVOKE_METHOD ) + { + DebuggerTlsData* tls = mono_wasm_get_tls(); + InvokeData invoke_data; + memset(&invoke_data, 0, sizeof(InvokeData)); + invoke_data.endp = data + size; + error = mono_do_invoke_method(tls, &buf, &invoke_data, data, &data); + } + else + error = mono_process_dbg_packet(id, command_set, command, &no_reply, data, data + size, &buf); + EM_ASM ({ + MONO.mono_wasm_add_dbg_command_received ($0, $1, $2, $3); + }, error == MDBGPROT_ERR_NONE, id, buf.buf, buf.p-buf.buf); + + buffer_free (&buf); + return TRUE; } -EMSCRIPTEN_KEEPALIVE int -mono_wasm_current_bp_id (void) +static gboolean +receive_debugger_agent_message (void *data, int len) { - PRINT_DEBUG_MSG (2, "COMPUTING breakpoint ID\n"); - //FIXME handle compiled case - - /* Interpreter */ - MonoLMF *lmf = mono_get_lmf (); - - g_assert (((guint64)lmf->previous_lmf) & 2); - MonoLMFExt *ext = (MonoLMFExt*)lmf; - - g_assert (ext->kind == MONO_LMFEXT_INTERP_EXIT || ext->kind == MONO_LMFEXT_INTERP_EXIT_WITH_CTX); - MonoInterpFrameHandle *frame = (MonoInterpFrameHandle*)ext->interp_exit_data; - MonoJitInfo *ji = mini_get_interp_callbacks ()->frame_get_jit_info (frame); - guint8 *ip = (guint8*)mini_get_interp_callbacks ()->frame_get_ip (frame); - - g_assert (ji && !ji->is_trampoline); - MonoMethod *method = jinfo_get_method (ji); - - /* Compute the native offset of the breakpoint from the ip */ - guint32 native_offset = ip - (guint8*)ji->code_start; - - MonoSeqPointInfo *info = NULL; - SeqPoint sp; - gboolean found_sp = mono_find_prev_seq_point_for_native_offset (method, native_offset, &info, &sp); - if (!found_sp) - PRINT_DEBUG_MSG (1, "Could not find SP\n"); - - - GPtrArray *bp_reqs = g_ptr_array_new (); - mono_de_collect_breakpoints_by_sp (&sp, ji, NULL, bp_reqs); - - if (bp_reqs->len == 0) { - PRINT_DEBUG_MSG (1, "BP NOT FOUND for method %s JI %p il_offset %d\n", method->name, ji, sp.il_offset); - return -1; - } - - if (bp_reqs->len > 1) - PRINT_DEBUG_MSG (1, "Multiple breakpoints (%d) at the same location, returning the first one.", bp_reqs->len); - - EventRequest *evt = (EventRequest *)g_ptr_array_index (bp_reqs, 0); - g_ptr_array_free (bp_reqs, TRUE); - - PRINT_DEBUG_MSG (1, "Found BP %p with id %d\n", evt, evt->id); - return evt->id; -} - -static MonoObject* -get_object_from_id (int objectId) -{ - ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId)); - if (!ref) { - PRINT_DEBUG_MSG (2, "get_object_from_id !ref: %d\n", objectId); - return NULL; - } - - MonoObject *obj = mono_gchandle_get_target_internal (ref->handle); - if (!obj) - PRINT_DEBUG_MSG (2, "get_object_from_id !obj: %d\n", objectId); - - return obj; -} - -static gboolean -list_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data) -{ - SeqPoint sp; - MonoMethod *method; - char *method_full_name; - - int* frame_id_p = (int*)data; - (*frame_id_p)++; - - //skip wrappers - if (info->type != FRAME_TYPE_MANAGED && info->type != FRAME_TYPE_INTERP) - return FALSE; - - if (info->ji) - method = jinfo_get_method (info->ji); - else - method = info->method; - - if (!method || method->wrapper_type != MONO_WRAPPER_NONE) - return FALSE; - - PRINT_DEBUG_MSG (2, "list_frames: Reporting method %s native_offset %d, wrapper_type: %d\n", method->name, info->native_offset, method->wrapper_type); - - if (!mono_find_prev_seq_point_for_native_offset (method, info->native_offset, NULL, &sp)) - PRINT_DEBUG_MSG (2, "list_frames: Failed to lookup sequence point. method: %s, native_offset: %d\n", method->name, info->native_offset); - - method_full_name = mono_method_full_name (method, FALSE); - while (method->is_inflated) - method = ((MonoMethodInflated*)method)->declaring; - - char *assembly_name = g_strdup (m_class_get_image (method->klass)->module_name); - inplace_tolower (assembly_name); - - PRINT_DEBUG_MSG (2, "adding off %d token %d assembly name %s\n", sp.il_offset, mono_metadata_token_index (method->token), assembly_name); - mono_wasm_add_frame (sp.il_offset, mono_metadata_token_index (method->token), *frame_id_p, assembly_name, method_full_name); - - g_free (assembly_name); - - return FALSE; -} - -EMSCRIPTEN_KEEPALIVE void -mono_wasm_enum_frames (void) -{ - int frame_id = -1; - mono_walk_stack_with_ctx (list_frames, NULL, MONO_UNWIND_NONE, &frame_id); -} - -static char* -invoke_to_string (const char *class_name, MonoClass *klass, gpointer addr) -{ - MonoObject *exc; - MonoString *mstr; - char *ret_str; - ERROR_DECL (error); - MonoObject *obj; - - // TODO: this is for a specific use case right now, - // (invoke ToString() get a preview/description for *some* types) - // and we don't want to report errors for that. - if (m_class_is_valuetype (klass)) { - MonoMethod *method; - - MONO_STATIC_POINTER_INIT (MonoMethod, to_string) - to_string = mono_class_get_method_from_name_checked (mono_get_object_class (), "ToString", 0, METHOD_ATTRIBUTE_VIRTUAL | METHOD_ATTRIBUTE_PUBLIC, error); - mono_error_assert_ok (error); - MONO_STATIC_POINTER_INIT_END (MonoMethod, to_string) - - method = mono_class_get_virtual_method (klass, to_string, error); - if (!method) - return NULL; - - MonoString *mstr = (MonoString*) mono_runtime_try_invoke_internal (method, addr , NULL, &exc, error); - if (exc || !is_ok (error)) { - PRINT_DEBUG_MSG (1, "Failed to invoke ToString for %s\n", class_name); - return NULL; - } - - return mono_string_to_utf8_checked_internal (mstr, error); - } - - obj = *(MonoObject**)addr; - if (!obj) - return NULL; - - mstr = mono_object_try_to_string (obj, &exc, error); - if (exc || !is_ok (error)) - return NULL; - - ret_str = mono_string_to_utf8_checked_internal (mstr, error); - if (!is_ok (error)) - return NULL; - - return ret_str; -} - -static char* -get_to_string_description (const char* class_name, MonoClass *klass, gpointer addr) -{ - if (!class_name || !klass || !addr) - return NULL; - - if (strcmp (class_name, "System.Guid") == 0) - return mono_guid_to_string (addr); - - for (int i = 0; i < G_N_ELEMENTS (to_string_as_descr_names); i ++) { - if (strcmp (to_string_as_descr_names [i], class_name) == 0) { - return invoke_to_string (class_name, klass, addr); - } - } - - return NULL; -} - -typedef struct { - int cur_frame; - int target_frame; - int len; - int *pos; - 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 - * - * :[]: - * - * .. which is consumed by `mono_wasm_add_func_var`. It is used for - * generating this for the delegate, and it's target. - */ -static char* -mono_method_to_desc_for_js (MonoMethod *method, gboolean include_namespace) -{ - MonoMethodSignature *sig = mono_method_signature_internal (method); - char *ret_desc = mono_type_full_name (sig->ret); - char *args_desc = mono_signature_get_desc (sig, include_namespace); - - char *sig_desc = g_strdup_printf ("%s:%s:%s", ret_desc, args_desc, method->name); - - g_free (ret_desc); - g_free (args_desc); - return sig_desc; -} - -static guint64 -read_enum_value (const char *mem, int type) -{ - switch (type) { - case MONO_TYPE_BOOLEAN: - case MONO_TYPE_U1: - return *(guint8*)mem; - case MONO_TYPE_I1: - return *(gint8*)mem; - case MONO_TYPE_CHAR: - case MONO_TYPE_U2: - return read16 (mem); - case MONO_TYPE_I2: - return (gint16) read16 (mem); - case MONO_TYPE_U4: - case MONO_TYPE_R4: - return read32 (mem); - case MONO_TYPE_I4: - return (gint32) read32 (mem); - case MONO_TYPE_U8: - case MONO_TYPE_I8: - case MONO_TYPE_R8: - return read64 (mem); - case MONO_TYPE_U: - case MONO_TYPE_I: -#if SIZEOF_REGISTER == 8 - return read64 (mem); -#else - return read32 (mem); -#endif - default: - g_assert_not_reached (); - } - return 0; -} - -static gboolean -nullable_try_get_value (guint8 *nullable, MonoClass *klass, gpointer* out_value) -{ - mono_class_setup_fields (klass); - g_assert (m_class_is_fields_inited (klass)); - - *out_value = NULL; - MonoClassField *klass_fields = m_class_get_fields (klass); - gpointer addr_for_has_value = mono_vtype_get_field_addr (nullable, &klass_fields[0]); - if (0 == *(guint8*)addr_for_has_value) - return FALSE; - - *out_value = mono_vtype_get_field_addr (nullable, &klass_fields[1]); - return TRUE; -} - -static gboolean -describe_value(MonoType * type, gpointer addr, int gpflags) -{ - ERROR_DECL (error); - switch (type->type) { - case MONO_TYPE_BOOLEAN: - mono_wasm_add_typed_value ("bool", NULL, *(gint8*)addr); - break; - case MONO_TYPE_I1: - mono_wasm_add_typed_value ("number", NULL, *(gint8*)addr); - break; - case MONO_TYPE_U1: - mono_wasm_add_typed_value ("number", NULL, *(guint8*)addr); - break; - case MONO_TYPE_CHAR: - mono_wasm_add_typed_value ("char", NULL, *(guint16*)addr); - break; - case MONO_TYPE_U2: - mono_wasm_add_typed_value ("number", NULL, *(guint16*)addr); - break; - case MONO_TYPE_I2: - mono_wasm_add_typed_value ("number", NULL, *(gint16*)addr); - break; - case MONO_TYPE_I4: - case MONO_TYPE_I: - mono_wasm_add_typed_value ("number", NULL, *(gint32*)addr); - break; - case MONO_TYPE_U4: - case MONO_TYPE_U: - mono_wasm_add_typed_value ("number", NULL, *(guint32*)addr); - break; - case MONO_TYPE_I8: - mono_wasm_add_typed_value ("number", NULL, *(gint64*)addr); - break; - case MONO_TYPE_U8: - mono_wasm_add_typed_value ("number", NULL, *(guint64*)addr); - break; - case MONO_TYPE_R4: - mono_wasm_add_typed_value ("number", NULL, *(float*)addr); - break; - case MONO_TYPE_R8: - mono_wasm_add_typed_value ("number", NULL, *(double*)addr); - break; - case MONO_TYPE_PTR: - case MONO_TYPE_FNPTR: { - char *class_name = mono_type_full_name (type); - const void *val = *(const void **)addr; - char *descr = g_strdup_printf ("(%s) %p", class_name, val); - - EM_ASM ({ - MONO.mono_wasm_add_typed_value ('pointer', $0, { ptr_addr: $1, klass_addr: $2 }); - }, descr, val ? addr : 0, val ? mono_class_from_mono_type_internal (type) : 0); - - g_free (descr); - g_free (class_name); - break; - } - - case MONO_TYPE_STRING: { - MonoString *str_obj = *(MonoString **)addr; - if (!str_obj) { - mono_wasm_add_typed_value ("string", NULL, 0); - } else { - char *str = mono_string_to_utf8_checked_internal (str_obj, error); - mono_error_assert_ok (error); /* FIXME report error */ - mono_wasm_add_typed_value ("string", str, 0); - g_free (str); - } - break; - } - - 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 - mono_wasm_add_obj_var ("object", NULL, 0); - break; - } - - type = m_class_get_byval_arg (klass); - if (type->type == MONO_TYPE_OBJECT) { - mono_wasm_add_obj_var ("object", "object", get_object_id (obj)); - break; - } - - // Boxed valuetype - if (m_class_is_valuetype (klass)) - addr = mono_object_unbox_internal (obj); - - return describe_value (type, addr, gpflags); - } - - case MONO_TYPE_GENERICINST: { - MonoClass *klass = mono_class_from_mono_type_internal (type); - if (mono_class_is_nullable (klass)) { - MonoType *targ = type->data.generic_class->context.class_inst->type_argv [0]; - - gpointer nullable_value = NULL; - if (nullable_try_get_value (addr, klass, &nullable_value)) { - return describe_value (targ, nullable_value, gpflags); - } else { - char* class_name = mono_type_full_name (type); - mono_wasm_add_obj_var (class_name, NULL, 0); - g_free (class_name); - break; - } - } - - if (mono_type_generic_inst_is_valuetype (type)) - goto handle_vtype; - /* - * else fallthrough - */ - } - - case MONO_TYPE_SZARRAY: - 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 = mono_object_unbox_internal (obj); - type = m_class_get_byval_arg (mono_object_class (obj)); - goto handle_vtype; - } - - char *class_name = mono_type_full_name (type); - int obj_id = get_object_id (obj); - - if (type-> type == MONO_TYPE_ARRAY || type->type == MONO_TYPE_SZARRAY) { - MonoArray *array = (MonoArray *)obj; - EM_ASM ({ - MONO.mono_wasm_add_typed_value ('array', $0, { objectId: $1, length: $2 }); - }, class_name, obj_id, mono_array_length_internal (array)); - } else if (m_class_is_delegate (klass) || (type->type == MONO_TYPE_GENERICINST && m_class_is_delegate (type->data.generic_class->container_class))) { - MonoMethod *method; - - if (type->type == MONO_TYPE_GENERICINST) - klass = type->data.generic_class->container_class; - - method = mono_get_delegate_invoke_internal (klass); - if (!method) { - mono_wasm_add_func_var (class_name, NULL, -1); - } else { - MonoMethod *tm = ((MonoDelegate *)obj)->method; - char *tm_desc = NULL; - if (tm) - tm_desc = mono_method_to_desc_for_js (tm, FALSE); - - mono_wasm_add_func_var (class_name, tm_desc, obj_id); - g_free (tm_desc); - } - } else { - char *to_string_val = get_to_string_description (class_name, klass, addr); - mono_wasm_add_obj_var (class_name, to_string_val, obj_id); - g_free (to_string_val); - } - g_free (class_name); - break; - } - - handle_vtype: - case MONO_TYPE_VALUETYPE: { - g_assert (addr); - MonoClass *klass = mono_class_from_mono_type_internal (type); - char *class_name = mono_type_full_name (type); - - if (m_class_is_enumtype (klass)) { - MonoClassField *field; - gpointer iter = NULL; - const char *p; - MonoTypeEnum def_type; - guint64 field_value; - guint64 value__ = 0xDEAD; - GString *enum_members = g_string_new (""); - int base_type = mono_class_enum_basetype_internal (klass)->type; - - while ((field = mono_class_get_fields_internal (klass, &iter))) { - if (strcmp ("value__", mono_field_get_name (field)) == 0) { - value__ = read_enum_value (mono_vtype_get_field_addr (addr, field), base_type); - continue; - } - - if (!(field->type->attrs & FIELD_ATTRIBUTE_STATIC)) - continue; - if (mono_field_is_deleted (field)) - continue; - - p = mono_class_get_field_default_value (field, &def_type); - /* this is to correctly increment `p` in the blob */ - /* len = */ mono_metadata_decode_blob_size (p, &p); - - field_value = read_enum_value (p, base_type); - - g_string_append_printf (enum_members, ",%s:%llu", mono_field_get_name (field), field_value); - } - - mono_wasm_add_enum_var (class_name, enum_members->str, value__); - g_string_free (enum_members, TRUE); - } else { - char *to_string_val = get_to_string_description (class_name, klass, addr); - - if (gpflags & GPFLAG_EXPAND_VALUETYPES) { - int32_t size = mono_class_value_size (klass, NULL); - void *value_buf = g_malloc0 (size); - mono_value_copy_internal (value_buf, addr, klass); - - EM_ASM ({ - MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2, value_addr: $3, value_size: $4, klass: $5 }); - }, "begin_vt", class_name, to_string_val, value_buf, size, klass); - - g_free (value_buf); - - // FIXME: isAsyncLocalThis - describe_object_properties_for_klass (addr, klass, FALSE, gpflags); - mono_wasm_add_typed_value ("end_vt", NULL, 0); - } else { - EM_ASM ({ - MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2 }); - }, "unexpanded_vt", class_name, to_string_val); - } - g_free (to_string_val); - } - g_free (class_name); - break; - } - default: { - char *type_name = mono_type_full_name (type); - char *msg = g_strdup_printf("can't handle type %s [%p, %x]", type_name, type, type->type); - mono_wasm_add_typed_value ("string", msg, 0); - g_free (msg); - g_free (type_name); - } - } - return TRUE; -} - -static gboolean -are_getters_allowed (const char *class_name) -{ - for (int i = 0; i < G_N_ELEMENTS (all_getters_allowed_class_names); i ++) { - if (strcmp (class_name, all_getters_allowed_class_names [i]) == 0) - return TRUE; - } - - return FALSE; -} - -static gboolean -invoke_and_describe_getter_value (MonoObject *obj, MonoProperty *p) -{ - ERROR_DECL (error); - MonoObject *res; - MonoObject *exc; - - MonoMethodSignature *sig = mono_method_signature_internal (p->get); - - res = mono_runtime_try_invoke_internal (p->get, obj, NULL, &exc, error); - if (!is_ok (error) && exc == NULL) - exc = (MonoObject *) mono_error_convert_to_exception (error); - if (exc) - { - const char *class_name = mono_class_full_name (mono_object_class (exc)); - ERROR_DECL (local_error); - char *str = mono_string_to_utf8_checked_internal (((MonoException*)exc)->message, local_error); - mono_error_assert_ok (local_error); /* FIXME report error */ - char *msg = g_strdup_printf("%s: %s", class_name, str); - mono_wasm_add_typed_value ("string", msg, 0); - g_free (msg); - return TRUE; - } - else if (!res || !m_class_is_valuetype (mono_object_class (res))) - return describe_value (sig->ret, &res, GPFLAG_EXPAND_VALUETYPES); - else - return describe_value (sig->ret, mono_object_unbox_internal (res), GPFLAG_EXPAND_VALUETYPES); -} - -static MonoObject* mono_runtime_try_invoke_internal (MonoMethod *method, void *obj, void **params, MonoObject **exc, MonoError* error) -{ - exception_on_runtime_invoke = NULL; - MonoObject* res = mono_runtime_try_invoke (method, obj, params, exc, error); - if (exception_on_runtime_invoke != NULL) - *exc = exception_on_runtime_invoke; - exception_on_runtime_invoke = NULL; - return res; -} - -static void -describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags) -{ - MonoClassField *f; - MonoProperty *p; - MonoMethodSignature *sig; - gboolean is_valuetype; - int pnum; - char *klass_name; - gboolean auto_invoke_getters; - gboolean is_own; - gboolean only_backing_fields; - - g_assert (klass); - MonoClass *start_klass = klass; - - only_backing_fields = gpflags & GPFLAG_ACCESSORS_ONLY; - is_valuetype = m_class_is_valuetype(klass); - if (is_valuetype) - gpflags |= GPFLAG_EXPAND_VALUETYPES; - -handle_parent: - is_own = (start_klass == klass); - klass_name = mono_class_full_name (klass); - gpointer iter = NULL; - while (obj && (f = mono_class_get_fields_internal (klass, &iter))) { - if (isAsyncLocalThis && f->name[0] == '<' && f->name[1] == '>') { - if (g_str_has_suffix (f->name, "__this")) { - mono_wasm_add_properties_var ("this", f->offset); - gpointer field_value = (guint8*)obj + f->offset; - - describe_value (f->type, field_value, gpflags); - } - - continue; - } - if (f->type->attrs & FIELD_ATTRIBUTE_STATIC) - continue; - if (mono_field_is_deleted (f)) - continue; - - if (only_backing_fields && !g_str_has_suffix(f->name, "k__BackingField")) - continue; - - EM_ASM ({ - MONO.mono_wasm_add_properties_var ($0, { field_offset: $1, is_own: $2, attr: $3, owner_class: $4 }); - }, f->name, f->offset, is_own, f->type->attrs, klass_name); - - gpointer field_addr; - if (is_valuetype) - field_addr = mono_vtype_get_field_addr (obj, f); - else - field_addr = (guint8*)obj + f->offset; - - describe_value (f->type, field_addr, gpflags); - } - - auto_invoke_getters = are_getters_allowed (klass_name); - iter = NULL; - pnum = 0; - while ((p = mono_class_get_properties (klass, &iter))) { - if (p->get->name) { //if get doesn't have name means that doesn't have a getter implemented and we don't want to show value, like VS debug - if (isAsyncLocalThis && (p->name[0] != '<' || (p->name[0] == '<' && p->name[1] == '>'))) - continue; - - sig = mono_method_signature_internal (p->get); - if (sig->param_count != 0) { - // getters with params are not shown - continue; - } - - if (p->get->flags & METHOD_ATTRIBUTE_STATIC) - continue; - - EM_ASM ({ - MONO.mono_wasm_add_properties_var ($0, { field_offset: $1, is_own: $2, attr: $3, owner_class: $4 }); - }, p->name, pnum, is_own, p->attrs, klass_name); - - gboolean vt_self_type_getter = is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass; - if (auto_invoke_getters && !vt_self_type_getter) { - invoke_and_describe_getter_value (obj, p); - } else { - // not allowed to call the getter here - char *ret_class_name = mono_class_full_name (mono_class_from_mono_type_internal (sig->ret)); - - mono_wasm_add_typed_value ("getter", ret_class_name, -1); - - g_free (ret_class_name); - continue; - } - } - pnum ++; - } - - g_free (klass_name); - - // ownProperties - // Note: ownProperties should mean that we return members of the klass itself, - // but we are going to ignore that here, because otherwise vscode/chrome don't - // seem to ask for inherited fields at all. - // if (!is_valuetype && !(gpflags & GPFLAG_OWN_PROPERTIES) && (klass = m_class_get_parent (klass))) - if (!is_valuetype && (klass = m_class_get_parent (klass))) - goto handle_parent; -} - -/* - * We return a `Target` property only for now. - * In future, we could add a `MethodInfo` too. - */ -static gboolean -describe_delegate_properties (MonoObject *obj) -{ - MonoClass *klass = mono_object_class(obj); - if (!m_class_is_delegate (klass)) - return FALSE; - - // Target, like in VS - what is this field supposed to be, anyway?? - MonoMethod *tm = ((MonoDelegate *)obj)->method; - char * sig_desc = mono_method_to_desc_for_js (tm, FALSE); - - mono_wasm_add_properties_var ("Target", -1); - mono_wasm_add_func_var (NULL, sig_desc, -1); - - g_free (sig_desc); - return TRUE; -} - -static gboolean -describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, int gpflags) -{ - PRINT_DEBUG_MSG (2, "describe_object_properties %llu, gpflags: %d\n", objectId, gpflags); - - MonoObject *obj = get_object_from_id (objectId); - if (!obj) - return FALSE; - - if (m_class_is_delegate (mono_object_class (obj))) { - // delegates get the same id format as regular objects - describe_delegate_properties (obj); - } else { - describe_object_properties_for_klass (obj, obj->vtable->klass, isAsyncLocalThis, gpflags); - } - - return TRUE; -} - -static gboolean -invoke_getter (void *obj_or_value, MonoClass *klass, const char *name) -{ - if (!obj_or_value || !klass || !name) { - PRINT_DEBUG_MSG (2, "invoke_getter: none of the arguments can be null"); - return FALSE; - } - - gpointer iter; -handle_parent: - iter = NULL; - MonoProperty *p; - while ((p = mono_class_get_properties (klass, &iter))) { - //if get doesn't have name means that doesn't have a getter implemented and we don't want to show value, like VS debug - if (!p->get->name || strcasecmp (p->name, name) != 0) - continue; - - invoke_and_describe_getter_value (obj_or_value, p); - return TRUE; - } - - if ((klass = m_class_get_parent(klass))) - goto handle_parent; - - return FALSE; -} - -static gboolean -describe_array_values (guint64 objectId, int startIdx, int count, int gpflags) -{ - if (count == 0) - return TRUE; - - int esize; - gpointer elem; - MonoArray *arr = (MonoArray*) get_object_from_id (objectId); - if (!arr) - return FALSE; - - MonoClass *klass = mono_object_class (arr); - MonoTypeEnum type = m_class_get_byval_arg (klass)->type; - if (type != MONO_TYPE_SZARRAY && type != MONO_TYPE_ARRAY) { - PRINT_DEBUG_MSG (1, "describe_array_values: object is not an array. type: 0x%x\n", type); - return FALSE; - } - - int len = arr->max_length; - if (len == 0 && startIdx == 0 && count <= 0) { - // Nothing to do - return TRUE; - } - - if (startIdx < 0 || (len > 0 && startIdx >= len)) { - PRINT_DEBUG_MSG (1, "describe_array_values: invalid startIdx (%d) for array of length %d\n", startIdx, len); - return FALSE; - } - - if (count > 0 && (startIdx + count) > len) { - PRINT_DEBUG_MSG (1, "describe_array_values: invalid count (%d) for startIdx: %d, and array of length %d\n", count, startIdx, len); - return FALSE; - } - - esize = mono_array_element_size (klass); - int endIdx = count < 0 ? len : startIdx + count; - - for (int i = startIdx; i < endIdx; i ++) { - mono_wasm_add_array_item(i); - elem = (gpointer*)((char*)arr->vector + (i * esize)); - describe_value (m_class_get_byval_arg (m_class_get_element_class (klass)), elem, gpflags); - } - return TRUE; -} - -static void -describe_async_method_locals (InterpFrame *frame, MonoMethod *method) -{ - //Async methods are special in the way that local variables can be lifted to generated class fields - gpointer addr = NULL; - if (mono_debug_lookup_method_async_debug_info (method)) { - addr = mini_get_interp_callbacks ()->frame_get_this (frame); - MonoObject *obj = *(MonoObject**)addr; - int objId = get_object_id (obj); - mono_wasm_set_is_async_method (objId); - describe_object_properties (objId, TRUE, GPFLAG_NONE); - } -} - -static void -describe_non_async_this (InterpFrame *frame, MonoMethod *method) -{ - gpointer addr = NULL; - if (mono_debug_lookup_method_async_debug_info (method)) - return; - - if (mono_method_signature_internal (method)->hasthis) { - addr = mini_get_interp_callbacks ()->frame_get_this (frame); - MonoObject *obj = *(MonoObject**)addr; - MonoClass *klass = method->klass; - MonoType *type = m_class_get_byval_arg (method->klass); - - mono_wasm_add_properties_var ("this", -1); - - if (m_class_is_valuetype (klass)) { - describe_value (type, obj, GPFLAG_EXPAND_VALUETYPES); - } else { - // this is an object, and we can retrieve the valuetypes in it later - // through the object id - describe_value (type, addr, GPFLAG_NONE); - } - } -} - -static gboolean -describe_variable (InterpFrame *frame, MonoMethod *method, MonoMethodHeader *header, int pos, int gpflags) -{ - MonoType *type = NULL; - gpointer addr = NULL; - if (pos < 0) { - MonoMethodSignature *sig = mono_method_signature_internal (method); - pos = -pos - 1; - - if (pos >= sig->param_count) { - PRINT_DEBUG_MSG(1, "BUG: describe_variable, trying to access param indexed %d, but the method (%s) has only %d params\n", pos, method->name, sig->param_count); - return FALSE; - } - - type = sig->params [pos]; - addr = mini_get_interp_callbacks ()->frame_get_arg (frame, pos); - } else { - if (pos >= header->num_locals) { - PRINT_DEBUG_MSG(1, "BUG: describe_variable, trying to access local indexed %d, but the method (%s) has only %d locals\n", pos, method->name, header->num_locals); - return FALSE; - } - - type = header->locals [pos]; - addr = mini_get_interp_callbacks ()->frame_get_local (frame, pos); - } - - 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); -} - -static gboolean -decode_value (MonoType *t, guint8 *addr, const char* variableValue) -{ - char* endptr; - errno = 0; - switch (t->type) { - case MONO_TYPE_BOOLEAN: - if (!strcasecmp (variableValue, "True")) - *(guint8*)addr = 1; - else if (!strcasecmp (variableValue, "False")) - *(guint8*)addr = 0; - else - return FALSE; - break; - case MONO_TYPE_CHAR: - if (strlen (variableValue) > 1) - return FALSE; - *(gunichar2*)addr = variableValue [0]; - break; - case MONO_TYPE_I1: { - intmax_t val = strtoimax (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - if (val >= -128 && val <= 127) - *(gint8*)addr = val; - else - return FALSE; - break; - } - case MONO_TYPE_U1: { - intmax_t val = strtoimax (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - if (val >= 0 && val <= 255) - *(guint8*)addr = val; - else - return FALSE; - break; - } - case MONO_TYPE_I2: { - intmax_t val = strtoimax (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - if (val >= -32768 && val <= 32767) - *(gint16*)addr = val; - else - return FALSE; - break; - } - case MONO_TYPE_U2: { - intmax_t val = strtoimax (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - if (val >= 0 && val <= 65535) - *(guint16*)addr = val; - else - return FALSE; - break; - } - case MONO_TYPE_I4: { - intmax_t val = strtoimax (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - if (val >= -2147483648 && val <= 2147483647) - *(gint32*)addr = val; - else - return FALSE; - break; - } - case MONO_TYPE_U4: { - intmax_t val = strtoimax (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - if (val >= 0 && val <= 4294967295) - *(guint32*)addr = val; - else - return FALSE; - break; - } - case MONO_TYPE_I8: { - long long val = strtoll (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - *(gint64*)addr = val; - break; - } - case MONO_TYPE_U8: { - long long val = strtoll (variableValue, &endptr, 10); - if (errno != 0) - return FALSE; - *(guint64*)addr = val; - break; - } - case MONO_TYPE_R4: { - gfloat val = strtof (variableValue, &endptr); - if (errno != 0) - return FALSE; - *(gfloat*)addr = val; - break; - } - case MONO_TYPE_R8: { - gdouble val = strtof (variableValue, &endptr); - if (errno != 0) - return FALSE; - *(gdouble*)addr = val; - break; - } - default: - 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); - - if (!header) { - mono_error_cleanup(error); - data->error = TRUE; - return TRUE; - } - - if (!sig) - goto exit_with_error; - - int pos = data->pos; - - if (pos < 0) { - pos = - pos - 1; - if (pos >= sig->param_count) - goto exit_with_error; - is_arg = TRUE; - t = sig->params [pos]; - } - else { - if (pos >= header->num_locals) - goto exit_with_error; - 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)) - goto exit_with_error; - - DbgEngineErrorCode errorCode = mono_de_set_interp_var (t, addr, val_buf); - if (errorCode != ERR_NONE) { - goto exit_with_error; - } - - mono_metadata_free_mh (header); - return TRUE; - -exit_with_error: - data->error = TRUE; - mono_metadata_free_mh (header); - return TRUE; -} - -static gboolean -describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointer ud) -{ - ERROR_DECL (error); - FrameDescData *data = (FrameDescData*)ud; - - ++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; - g_assert (frame); - MonoMethod *method = frame->imethod->method; - g_assert (method); - - MonoMethodHeader *header = mono_method_get_header_checked (method, error); - mono_error_assert_ok (error); /* FIXME report error */ - - for (int i = 0; i < data->len; i++) - { - if (!describe_variable (frame, method, header, data->pos[i], GPFLAG_EXPAND_VALUETYPES)) - mono_wasm_add_typed_value("symbol", "", 0); - } - - describe_async_method_locals (frame, method); - describe_non_async_this (frame, method); - - mono_metadata_free_mh (header); - return TRUE; -} - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_set_variable_on_frame (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) -{ - MonoType *type = m_class_get_byval_arg (klass); - if (type->type != MONO_TYPE_PTR && type->type != MONO_TYPE_FNPTR) { - PRINT_DEBUG_MSG (2, "BUG: mono_wasm_get_deref_ptr_value: Expected to get a ptr type, but got 0x%x\n", type->type); - return FALSE; - } - - mono_wasm_add_properties_var ("deref", -1); - return describe_value (type->data.type, value_addr, GPFLAG_EXPAND_VALUETYPES); -} - -//FIXME this doesn't support getting the return value pseudo-var -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_get_local_vars (int scope, int* pos, int len) -{ - if (scope < 0) - return FALSE; - - FrameDescData data; - data.target_frame = scope; - data.cur_frame = -1; - data.len = len; - data.pos = pos; - data.found = FALSE; - - mono_walk_stack_with_ctx (describe_variables_on_frame, NULL, MONO_UNWIND_NONE, &data); - - return data.found; -} - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_get_object_properties (int object_id, int gpflags) -{ - PRINT_DEBUG_MSG (2, "getting properties of object %d, gpflags: %d\n", object_id, gpflags); - - return describe_object_properties (object_id, FALSE, gpflags); -} - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_get_array_values (int object_id, int start_idx, int count, int gpflags) -{ - PRINT_DEBUG_MSG (2, "getting array values %d, startIdx: %d, count: %d, gpflags: 0x%x\n", object_id, start_idx, count, gpflags); - - return describe_array_values (object_id, start_idx, count, gpflags); -} - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_invoke_getter_on_object (int object_id, const char* name) -{ - MonoObject *obj = get_object_from_id (object_id); - if (!obj) - return FALSE; - - 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; - } - DbgEngineErrorCode errorCode = mono_de_set_interp_var (f->type, (guint8*)obj + f->offset, val_buf); - if (errorCode != ERR_NONE) { - return FALSE; - } - 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) - break; - 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) { - char *error_message = mono_string_to_utf8_checked_internal (((MonoException *)exc)->message, error); - if (is_ok (error)) { - PRINT_DEBUG_MSG (2, "mono_wasm_set_value_on_object exception: %s\n", error_message); - g_free (error_message); - mono_error_cleanup (error); - } - else { - PRINT_DEBUG_MSG (2, "mono_wasm_set_value_on_object exception\n"); - } - return FALSE; - } - return TRUE; - } - - if ((klass = m_class_get_parent(klass))) - goto handle_parent; + EM_ASM ({ + MONO.mono_wasm_add_dbg_command_received (1, -1, $0, $1); + }, data, len); + mono_wasm_save_thread_context(); + mono_wasm_fire_debugger_agent_message (); return FALSE; } -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name) -{ - PRINT_DEBUG_MSG (2, "mono_wasm_invoke_getter_on_value: v: %p klass: %p, name: %s\n", value, klass, name); - if (!klass || !value) - return FALSE; - - if (!m_class_is_valuetype (klass)) { - PRINT_DEBUG_MSG (2, "mono_wasm_invoke_getter_on_value: klass is not a valuetype. name: %s\n", mono_class_full_name (klass)); - return FALSE; - } - - return invoke_getter (value, klass, name); -} - -EMSCRIPTEN_KEEPALIVE void -mono_wasm_set_is_debugger_attached (gboolean is_attached) -{ - mono_set_is_debugger_attached (is_attached); - if (is_attached && has_pending_lazy_loaded_assemblies) - { - GPtrArray *assemblies = mono_alc_get_all_loaded_assemblies (); - for (int i = 0; i < assemblies->len; ++i) { - MonoAssembly *ass = (MonoAssembly*)g_ptr_array_index (assemblies, i); - assembly_loaded (NULL, ass); - } - g_ptr_array_free (assemblies, TRUE); - has_pending_lazy_loaded_assemblies = FALSE; - } -} - -// Functions required by debugger-state-machine. -gsize -mono_debugger_tls_thread_id (DebuggerTlsData *debuggerTlsData) -{ - return 1; -} - #else // HOST_WASM void diff --git a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj index 2b05836627673c..15386dece90a91 100644 --- a/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj +++ b/src/mono/sample/wasm/browser/Wasm.Browser.Sample.csproj @@ -1,7 +1,11 @@ + Debug true runtime.js + true + embedded + 1 diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 1f31e044653ab9..81c419e4c5850e 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -317,6 +317,7 @@ internal class MethodInfo public SourceId SourceId => source.SourceId; + public int DebuggerId { get; set; } public string Name { get; } public MethodDebugInformation DebugInformation; public MethodDefinitionHandle methodDefHandle; @@ -325,7 +326,7 @@ internal class MethodInfo public SourceLocation EndLocation { get; } public AssemblyInfo Assembly { get; } public int Token { get; } - + public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0; public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type) { this.Assembly = assembly; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index efb2a7b4a2a796..f39f2736caaebf 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -172,50 +172,33 @@ internal class MonoCommands public MonoCommands(string expression) => this.expression = expression; - public static MonoCommands GetCallStack() => new MonoCommands("MONO.mono_wasm_get_call_stack()"); - public static MonoCommands GetExceptionObject() => new MonoCommands("MONO.mono_wasm_get_exception_object()"); - public static MonoCommands IsRuntimeReady() => new MonoCommands("MONO.mono_wasm_runtime_is_ready"); + public static MonoCommands GetDebuggerAgentBufferReceived() => new MonoCommands("MONO.mono_wasm_get_dbg_command_info()"); - public static MonoCommands StartSingleStepping(StepKind kind) => new MonoCommands($"MONO.mono_wasm_start_single_stepping ({(int)kind})"); + public static MonoCommands IsRuntimeReady() => new MonoCommands("MONO.mono_wasm_runtime_is_ready"); public static MonoCommands GetLoadedFiles() => new MonoCommands("MONO.mono_wasm_get_loaded_files()"); - public static MonoCommands ClearAllBreakpoints() => new MonoCommands("MONO.mono_wasm_clear_all_breakpoints()"); - - public static MonoCommands GetDetails(DotnetObjectId objectId, JToken args = null) => new MonoCommands($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{ }")})"); - - public static MonoCommands GetScopeVariables(int scopeId, params VarInfo[] vars) - { - var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray(); - 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) + public static MonoCommands SendDebuggerAgentCommand(int id, int command_set, int command, string command_parameters) { - return new MonoCommands($"MONO.mono_wasm_set_variable_value({scopeId}, {index}, '{name}', '{newValue}')"); + return new MonoCommands($"MONO.mono_wasm_send_dbg_command ({id}, {command_set}, {command},'{command_parameters}')"); } - public static MonoCommands EvaluateMemberAccess(int scopeId, string expr, params VarInfo[] vars) + public static MonoCommands SendDebuggerAgentCommandWithParms(int id, int command_set, int command, string command_parameters, int len, int type, string parm) { - var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray(); - return new MonoCommands($"MONO.mono_wasm_eval_member_access({scopeId}, {JsonConvert.SerializeObject(var_ids)}, '', '{expr}')"); + return new MonoCommands($"MONO.mono_wasm_send_dbg_command_with_parms ({id}, {command_set}, {command},'{command_parameters}', {len}, {type}, '{parm}')"); } - public static MonoCommands SetBreakpoint(string assemblyName, int methodToken, int ilOffset) => new MonoCommands($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})"); - - public static MonoCommands RemoveBreakpoint(int breakpointId) => new MonoCommands($"MONO.mono_wasm_remove_breakpoint({breakpointId})"); - - public static MonoCommands ReleaseObject(DotnetObjectId objectId) => new MonoCommands($"MONO.mono_wasm_release_object('{objectId}')"); - public static MonoCommands CallFunctionOn(JToken args) => new MonoCommands($"MONO.mono_wasm_call_function_on ({args.ToString()})"); - public static MonoCommands Resume() => new MonoCommands($"MONO.mono_wasm_debugger_resume ()"); + public static MonoCommands GetDetails(int objectId, JToken args = null) => new MonoCommands($"MONO.mono_wasm_get_details ({objectId}, {(args ?? "{ }")})"); - public static MonoCommands SetPauseOnExceptions(string state) => new MonoCommands($"MONO.mono_wasm_set_pause_on_exceptions(\"{state}\")"); + public static MonoCommands Resume() => new MonoCommands($"MONO.mono_wasm_debugger_resume ()"); public static MonoCommands DetachDebugger() => new MonoCommands($"MONO.mono_wasm_detach_debugger()"); + + public static MonoCommands ReleaseObject(DotnetObjectId objectId) => new MonoCommands($"MONO.mono_wasm_release_object('{objectId}')"); } internal enum MonoErrorCodes @@ -280,8 +263,8 @@ internal enum BreakpointState internal enum StepKind { Into, - Out, - Over + Over, + Out } internal class ExecutionContext @@ -292,6 +275,7 @@ internal class ExecutionContext public TaskCompletionSource ready; public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; + public int ThreadId { get; set; } public int Id { get; set; } public object AuxData { get; set; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index 21f8dabdb37a63..c4e578a0a2c920 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.IO; namespace Microsoft.WebAssembly.Diagnostics { @@ -18,7 +19,6 @@ internal class MemberReferenceResolver private MonoProxy proxy; private ExecutionContext ctx; private PerScopeCache scopeCache; - private VarInfo[] varIds; private ILogger logger; private bool locals_fetched; @@ -31,44 +31,99 @@ public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId this.logger = logger; scopeCache = ctx.GetCacheForScope(scope_id); } - - // Checks Locals, followed by `this` - public async Task Resolve(string var_name, CancellationToken token) + public async Task GetValueFromObject(JToken objRet, CancellationToken token) { - if (scopeCache.Locals.Count == 0 && !locals_fetched) + if (objRet["value"]?["className"]?.Value() == "System.Exception") { - Result scope_res = await proxy.GetScopeProperties(sessionId, scopeId, token); - if (scope_res.IsErr) - throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}"); - locals_fetched = true; + if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) + { + var exceptionObject = await proxy.sdbHelper.GetObjectValues(sessionId, int.Parse(objectId.Value), true, false, false, true, token); + var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); + exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); + return exceptionObjectMessage["value"]?.Value(); + } + return objRet["value"]?.Value(); } - if (scopeCache.Locals.TryGetValue(var_name, out JObject obj)) + if (objRet["value"]?.Value() != null) + return objRet["value"]?.Value(); + if (objRet["get"]?.Value() != null) { - return obj["value"]?.Value(); - } + if (DotnetObjectId.TryParse(objRet?["get"]?["objectIdValue"]?.Value(), out DotnetObjectId objectId)) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.WriteObj(objectId, proxy.sdbHelper); + var ret = await proxy.sdbHelper.InvokeMethod(sessionId, command_params.ToArray(), objRet["get"]["methodId"].Value(), objRet["name"].Value(), token); + return await GetValueFromObject(ret, token); + } - if (scopeCache.MemberReferences.TryGetValue(var_name, out JObject ret)) - return ret; - - if (varIds == null) - { - Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId); - varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); } + return null; + } + // Checks Locals, followed by `this` + public async Task Resolve(string var_name, CancellationToken token) + { + string[] parts = var_name.Split("."); + JObject rootObject = null; - Result res = await proxy.SendMonoCommand(sessionId, MonoCommands.EvaluateMemberAccess(scopeId, var_name, varIds), token); - if (res.IsOk) - { - ret = res.Value?["result"]?["value"]?["value"]?.Value(); - scopeCache.MemberReferences[var_name] = ret; + if (scopeCache.MemberReferences.TryGetValue(var_name, out JObject ret)) { + return ret; } - else + foreach (string part in parts) { - logger.LogDebug(res.Error.ToString()); - } + string partTrimmed = part.Trim(); + if (partTrimmed == "") + return null; + if (rootObject != null) + { + if (rootObject?["subtype"]?.Value() == "null") + return null; + if (DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) + { + var root_res_obj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + var objRet = root_res_obj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == partTrimmed); + if (objRet == null) + return null; - return ret; + rootObject = await GetValueFromObject(objRet, token); + } + continue; + } + if (scopeCache.Locals.Count == 0 && !locals_fetched) + { + Result scope_res = await proxy.GetScopeProperties(sessionId, scopeId, token); + if (scope_res.IsErr) + throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}"); + locals_fetched = true; + } + if (scopeCache.Locals.TryGetValue(partTrimmed, out JObject obj)) + { + rootObject = obj["value"]?.Value(); + } + else if (scopeCache.Locals.TryGetValue("this", out JObject objThis)) + { + if (partTrimmed == "this") + { + rootObject = objThis?["value"].Value(); + } + else if (DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) + { + var root_res_obj = await proxy.RuntimeGetPropertiesInternal(sessionId, objectId, null, token); + var objRet = root_res_obj.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == partTrimmed); + if (objRet != null) + { + rootObject = await GetValueFromObject(objRet, token); + } + else + { + return null; + } + } + } + } + scopeCache.MemberReferences[var_name] = rootObject; + return rootObject; } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index b80a4483ca02da..689904740d3d66 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -18,6 +18,7 @@ namespace Microsoft.WebAssembly.Diagnostics { internal class MonoProxy : DevToolsProxy { + internal MonoSDBHelper sdbHelper; private IList urlSymbolServerList; private static HttpClient client = new HttpClient(); private HashSet sessions = new HashSet(); @@ -26,6 +27,7 @@ internal class MonoProxy : DevToolsProxy public MonoProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList) : base(loggerFactory) { this.urlSymbolServerList = urlSymbolServerList ?? new List(); + sdbHelper = new MonoSDBHelper(this); } internal ExecutionContext GetContext(SessionId sessionId) @@ -132,11 +134,15 @@ protected override async Task AcceptEvent(SessionId sessionId, string meth await SendCommand(sessionId, "Debugger.resume", new JObject(), token); return true; } - case "mono_wasm_fire_bp": - case "_mono_wasm_fire_bp": - case "_mono_wasm_fire_exception": + case "_mono_wasm_fire_debugger_agent_message": { - return await OnPause(sessionId, args, token); + try { + return await OnReceiveDebuggerAgentEvent(sessionId, args, token); + } + catch (Exception) //if the page is refreshed maybe it stops here. + { + return false; + } } } break; @@ -370,8 +376,12 @@ protected override async Task AcceptCommand(MessageId id, string method, J if (!DotnetObjectId.TryParse(args?["objectId"], out DotnetObjectId objectId)) break; - Result result = await RuntimeGetProperties(id, objectId, args, token); - SendResponse(id, result, token); + var ret = await RuntimeGetPropertiesInternal(id, objectId, args, token); + if (ret == null) { + SendResponse(id, Result.Err($"Unable to RuntimeGetProperties '{objectId}'"), token); + } + else + SendResponse(id, Result.OkFromObject(new { result = ret }), token); return true; } @@ -388,7 +398,7 @@ protected override async Task AcceptCommand(MessageId id, string method, J case "Debugger.setPauseOnExceptions": { string state = args["state"].Value(); - await SendMonoCommand(id, MonoCommands.SetPauseOnExceptions(state), token); + await sdbHelper.EnableExceptions(id, state, token); // Pass this on to JS too return false; } @@ -403,8 +413,6 @@ protected override async Task AcceptCommand(MessageId id, string method, J } case "DotnetDebugger.getMethodLocation": { - Console.WriteLine("set-breakpoint-by-method: " + id + " " + args); - DebugStore store = await RuntimeReady(id, token); string aname = args["assemblyName"]?.Value(); string typeName = args["typeName"]?.Value(); @@ -460,31 +468,80 @@ protected override async Task AcceptCommand(MessageId id, string method, J } case "Runtime.callFunctionOn": { - if (!DotnetObjectId.TryParse(args["objectId"], out DotnetObjectId objectId)) - return false; - - if (objectId.Scheme == "scope") - { + try { + return await CallOnFunction(id, args, token); + } + catch (Exception){ SendResponse(id, Result.Exception(new ArgumentException( - $"Runtime.callFunctionOn not supported with scope ({objectId}).")), + $"Runtime.callFunctionOn not supported with ({args["objectId"]}).")), token); return true; } - - Result res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token); - JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type; - - if (res.IsOk && res_value_type == JTokenType.Object || res_value_type == JTokenType.Object) - res = Result.OkFromObject(new { result = res.Value["result"]["value"] }); - - SendResponse(id, res, token); - return true; } } return false; } + private async Task CallOnFunction(MessageId id, JObject args, CancellationToken token) + { + if (!DotnetObjectId.TryParse(args["objectId"], out DotnetObjectId objectId)) { + return false; + } + switch (objectId.Scheme) + { + case "object": + args["details"] = await sdbHelper.GetObjectProxy(id, int.Parse(objectId.Value), token); + break; + case "valuetype": + args["details"] = await sdbHelper.GetValueTypeProxy(id, int.Parse(objectId.Value), token); + break; + case "pointer": + args["details"] = await sdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token); + break; + case "array": + args["details"] = await sdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token); + break; + case "cfo_res": + { + Result cfo_res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token); + cfo_res = Result.OkFromObject(new { result = cfo_res.Value?["result"]?["value"]}); + SendResponse(id, cfo_res, token); + return true; + } + case "scope": + { + SendResponse(id, + Result.Exception(new ArgumentException( + $"Runtime.callFunctionOn not supported with scope ({objectId}).")), + token); + return true; + } + default: + return false; + } + Result res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(args), token); + if (res.IsErr) + { + SendResponse(id, res, token); + return true; + } + if (res.Value?["result"]?["value"]?["type"] == null) //it means that is not a buffer returned from the debugger-agent + { + byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); + var ret_debugger_cmd = new MemoryStream(newBytes); + var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); + ret_debugger_cmd_reader.ReadByte(); //number of objects returned. + var obj = await sdbHelper.CreateJObjectForVariableValue(id, ret_debugger_cmd_reader, "ret", false, -1, token); + /*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/ + res = Result.OkFromObject(new { result = obj["value"]}); + SendResponse(id, res, token); + return true; + } + res = Result.OkFromObject(new { result = res.Value?["result"]?["value"]}); + SendResponse(id, res, token); + return true; + } private async Task OnSetVariableValue(MessageId id, int scopeId, string varName, JToken varValue, CancellationToken token) { @@ -498,50 +555,59 @@ private async Task OnSetVariableValue(MessageId id, int scopeId, string va var varToSetValue = varIds.FirstOrDefault(v => v.Name == varName); if (varToSetValue == null) return false; - Result res = await SendMonoCommand(id, MonoCommands.SetVariableValue(scopeId, varToSetValue.Index, varName, varValue["value"].Value()), token); - if (res.IsOk) + var res = await sdbHelper.SetVariableValue(id, ctx.ThreadId, scopeId, varToSetValue.Index, varValue["value"].Value(), token); + if (res) SendResponse(id, Result.Ok(new JObject()), token); else SendResponse(id, Result.Err($"Unable to set '{varValue["value"].Value()}' to variable '{varName}'"), token); return true; } - private async Task RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token) + internal async Task RuntimeGetPropertiesInternal(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token) { - if (objectId.Scheme == "scope") + var accessorPropertiesOnly = false; + var ownProperties = false; + if (args != null) { - return await GetScopeProperties(id, int.Parse(objectId.Value), token); + if (args["accessorPropertiesOnly"] != null) + accessorPropertiesOnly = args["accessorPropertiesOnly"].Value(); + if (args["ownProperties"] != null) + ownProperties = args["ownProperties"].Value(); } - - Result res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token); - if (res.IsErr) - return res; - - if (objectId.Scheme == "cfo_res") - { - // Runtime.callFunctionOn result object - string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); - if (value_json_str != null) + //Console.WriteLine($"RuntimeGetProperties - {args}"); + try { + switch (objectId.Scheme) { - res = Result.OkFromObject(new + case "scope": { - result = JArray.Parse(value_json_str) - }); - } - else - { - res = Result.OkFromObject(new { result = new { } }); + var res = await GetScopeProperties(id, int.Parse(objectId.Value), token); + return res.Value?["result"]; + } + case "valuetype": + return await sdbHelper.GetValueTypeValues(id, int.Parse(objectId.Value), accessorPropertiesOnly, token); + case "array": + return await sdbHelper.GetArrayValues(id, int.Parse(objectId.Value), token); + case "object": + return await sdbHelper.GetObjectValues(id, int.Parse(objectId.Value), true, false, accessorPropertiesOnly, ownProperties, token); + case "pointer": + return new JArray{await sdbHelper.GetPointerContent(id, int.Parse(objectId.Value), token)}; + case "cfo_res": + { + Result res = await SendMonoCommand(id, MonoCommands.GetDetails(int.Parse(objectId.Value), args), token); + string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); + return value_json_str != null ? JArray.Parse(value_json_str) : null; + } + default: + return null; + } } - else - { - res = Result.Ok(JObject.FromObject(new { result = res.Value["result"]["value"] })); + catch (Exception) { + return null; } - - return res; } - private async Task EvaluateCondition(SessionId sessionId, ExecutionContext context, JObject mono_frame, Breakpoint bp, CancellationToken token) + private async Task EvaluateCondition(SessionId sessionId, ExecutionContext context, Frame mono_frame, Breakpoint bp, CancellationToken token) { if (string.IsNullOrEmpty(bp?.Condition) || mono_frame == null) return true; @@ -551,8 +617,7 @@ private async Task EvaluateCondition(SessionId sessionId, ExecutionContext if (bp.ConditionAlreadyEvaluatedWithError) return false; try { - var resolver = new MemberReferenceResolver(this, context, sessionId, mono_frame["frame_id"].Value(), logger); - + var resolver = new MemberReferenceResolver(this, context, sessionId, mono_frame.Id, logger); JObject retValue = await resolver.Resolve(condition, token); if (retValue == null) retValue = await EvaluateExpression.CompileAndRunTheExpression(condition, resolver, token); @@ -573,172 +638,124 @@ private async Task EvaluateCondition(SessionId sessionId, ExecutionContext } return false; } - - private async Task OnPause(SessionId sessionId, JObject args, CancellationToken token) + private async Task SendCallStack(SessionId sessionId, ExecutionContext context, string reason, int thread_id, Breakpoint bp, JObject data, IEnumerable orig_callframes, CancellationToken token) { - //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime - Result res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token); - IEnumerable orig_callframes = args?["callFrames"]?.Values(); - ExecutionContext context = GetContext(sessionId); - JObject data = null; - string reason = "other";//other means breakpoint - - if (res.IsErr) - { - //Give up and send the original call stack - return false; - } - - //step one, figure out where did we hit - JToken res_value = res.Value?["result"]?["value"]; - if (res_value == null || res_value is JValue) - { - //Give up and send the original call stack - return false; - } - - Log("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}"); - int? bp_id = res_value?["breakpoint_id"]?.Value(); - Log("verbose", $"We just hit bp {bp_id}"); - if (!bp_id.HasValue) - { - //Give up and send the original call stack - return false; - } - - Breakpoint bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == bp_id.Value); - var callFrames = new List(); - foreach (JObject frame in orig_callframes) - { - string function_name = frame["functionName"]?.Value(); - string url = frame["url"]?.Value(); - if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name || - "_mono_wasm_fire_exception" == function_name) + var frames = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(thread_id); + command_params_writer.Write(0); + command_params_writer.Write(-1); + var ret_debugger_cmd_reader = await sdbHelper.SendDebuggerAgentCommand(sessionId, CmdThread.GetFrameInfo, command_params, token); + var frame_count = ret_debugger_cmd_reader.ReadInt32(); + //Console.WriteLine("frame_count - " + frame_count); + for (int j = 0; j < frame_count; j++) { + var frame_id = ret_debugger_cmd_reader.ReadInt32(); + var method_id = ret_debugger_cmd_reader.ReadInt32(); + var il_pos = ret_debugger_cmd_reader.ReadInt32(); + var flags = ret_debugger_cmd_reader.ReadByte(); + var method_token = await sdbHelper.GetMethodToken(sessionId, method_id, token); + var assembly_id = await sdbHelper.GetAssemblyIdFromMethod(sessionId, method_id, token); + var assembly_name = await sdbHelper.GetAssemblyName(sessionId, assembly_id, token); + var method_name = await sdbHelper.GetMethodName(sessionId, method_id, token); + DebugStore store = await LoadStore(sessionId, token); + AssemblyInfo asm = store.GetAssemblyByName(assembly_name); + if (asm == null) { - if ("_mono_wasm_fire_exception" == function_name) + assembly_name = await sdbHelper.GetAssemblyNameFull(sessionId, assembly_id, token); //maybe is a lazy loaded assembly + asm = store.GetAssemblyByName(assembly_name); + if (asm == null) { - Result exception_obj_id = await SendMonoCommand(sessionId, MonoCommands.GetExceptionObject(), token); - JToken res_val = exception_obj_id.Value?["result"]?["value"]; - var exception_dotnet_obj_id = new DotnetObjectId("object", res_val?["exception_id"]?.Value()); - data = JObject.FromObject(new - { - type = "object", - subtype = "error", - className = res_val?["class_name"]?.Value(), - uncaught = res_val?["uncaught"]?.Value(), - description = res_val?["message"]?.Value() + "\n", - objectId = exception_dotnet_obj_id.ToString() - }); - reason = "exception"; + Log("debug", $"Unable to find assembly: {assembly_name}"); + continue; } + } - var frames = new List(); - IEnumerable the_mono_frames = res.Value?["result"]?["value"]?["frames"]?.Values(); + MethodInfo method = asm.GetMethodByToken(method_token); - foreach (JObject mono_frame in the_mono_frames) + if (method == null && !asm.HasSymbols) + { + try { - int frame_id = mono_frame["frame_id"].Value(); - int il_pos = mono_frame["il_pos"].Value(); - int method_token = mono_frame["method_token"].Value(); - string assembly_name = mono_frame["assembly_name"].Value(); + method = await LoadSymbolsOnDemand(asm, method_token, sessionId, token); + } + catch (Exception e) + { + Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name} exception: {e}"); + continue; + } + } - // This can be different than `method.Name`, like in case of generic methods - string method_name = mono_frame["method_name"]?.Value(); + if (method == null) + { + Log("debug", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); + continue; + } - DebugStore store = await LoadStore(sessionId, token); - AssemblyInfo asm = store.GetAssemblyByName(assembly_name); - if (asm == null) - { - Log("debug", $"Unable to find assembly: {assembly_name}"); - continue; - } + method.DebuggerId = method_id; - MethodInfo method = asm.GetMethodByToken(method_token); + SourceLocation location = method?.GetLocationByIl(il_pos); - if (method == null && !asm.HasSymbols) - { - try - { - method = await LoadSymbolsOnDemand(asm, method_token, sessionId, token); - } - catch (Exception e) - { - Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name} exception: {e}"); - continue; - } - } + // When hitting a breakpoint on the "IncrementCount" method in the standard + // Blazor project template, one of the stack frames is inside mscorlib.dll + // and we get location==null for it. It will trigger a NullReferenceException + // if we don't skip over that stack frame. + if (location == null) + { + continue; + } - if (method == null) - { - Log("debug", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); - continue; - } + Log("debug", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); + Log("debug", $"\tmethod {method_name} location: {location}"); + frames.Add(new Frame(method, location, frame_id)); - SourceLocation location = method?.GetLocationByIl(il_pos); + callFrames.Add(new + { + functionName = method_name, + callFrameId = $"dotnet:scope:{frame_id}", + functionLocation = method.StartLocation.AsLocation(), - // When hitting a breakpoint on the "IncrementCount" method in the standard - // Blazor project template, one of the stack frames is inside mscorlib.dll - // and we get location==null for it. It will trigger a NullReferenceException - // if we don't skip over that stack frame. - if (location == null) - { - continue; - } + location = location.AsLocation(), - Log("debug", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); - Log("debug", $"\tmethod {method_name} location: {location}"); - frames.Add(new Frame(method, location, frame_id)); + url = store.ToUrl(location), - callFrames.Add(new + scopeChain = new[] { - functionName = method_name, - callFrameId = $"dotnet:scope:{frame_id}", - functionLocation = method.StartLocation.AsLocation(), - - location = location.AsLocation(), - - url = store.ToUrl(location), - - scopeChain = new[] - { - new + new + { + type = "local", + @object = new { - type = "local", - @object = new - { - @type = "object", - className = "Object", - description = "Object", - objectId = $"dotnet:scope:{frame_id}", - }, - name = method_name, - startLocation = method.StartLocation.AsLocation(), - endLocation = method.EndLocation.AsLocation(), - } - } - }); - - context.CallStack = frames; + @type = "object", + className = "Object", + description = "Object", + objectId = $"dotnet:scope:{frame_id}", + }, + name = method_name, + startLocation = method.StartLocation.AsLocation(), + endLocation = method.EndLocation.AsLocation(), + } + } + }); - } - if (!await EvaluateCondition(sessionId, context, the_mono_frames?.First(), bp, token)) - { - await SendCommand(sessionId, "Debugger.resume", new JObject(), token); - return true; - } - } - else if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) || - url.StartsWith("wasm://wasm/", StringComparison.Ordinal))) - { - callFrames.Add(frame); - } + context.CallStack = frames; + context.ThreadId = thread_id; } - string[] bp_list = new string[bp == null ? 0 : 1]; if (bp != null) bp_list[0] = bp.StackId; + foreach (JObject frame in orig_callframes) + { + string function_name = frame["functionName"]?.Value(); + string url = frame["url"]?.Value(); + if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) || + url.StartsWith("wasm://wasm/", StringComparison.Ordinal) || function_name == "_mono_wasm_fire_debugger_agent_message")) + { + callFrames.Add(frame); + } + } var o = JObject.FromObject(new { callFrames, @@ -746,10 +763,72 @@ private async Task OnPause(SessionId sessionId, JObject args, Cancellation data, hitBreakpoints = bp_list, }); - + if (!await EvaluateCondition(sessionId, context, context.CallStack.First(), bp, token)) + { + await SendCommand(sessionId, "Debugger.resume", new JObject(), token); + return true; + } SendEvent(sessionId, "Debugger.paused", o, token); + return true; } + private async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, CancellationToken token) + { + Result res = await SendMonoCommand(sessionId, MonoCommands.GetDebuggerAgentBufferReceived(), token); + if (res.IsErr) + return false; + + ExecutionContext context = GetContext(sessionId); + byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); + var ret_debugger_cmd = new MemoryStream(newBytes); + var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); + ret_debugger_cmd_reader.ReadBytes(11); //skip HEADER_LEN + ret_debugger_cmd_reader.ReadByte(); //suspend_policy + var number_of_events = ret_debugger_cmd_reader.ReadInt32(); //number of events -> should be always one + for (int i = 0 ; i < number_of_events; i++) { + var event_kind = (EventKind)ret_debugger_cmd_reader.ReadByte(); //event kind + var request_id = ret_debugger_cmd_reader.ReadInt32(); //request id + if (event_kind == EventKind.Step) + await sdbHelper.ClearSingleStep(sessionId, request_id, token); + int thread_id = ret_debugger_cmd_reader.ReadInt32(); + switch (event_kind) + { + case EventKind.Exception: + { + string reason = "exception"; + int object_id = ret_debugger_cmd_reader.ReadInt32(); + var caught = ret_debugger_cmd_reader.ReadByte(); + var exceptionObject = await sdbHelper.GetObjectValues(sessionId, object_id, true, false, false, true, token); + var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("message")); + var data = JObject.FromObject(new + { + type = "object", + subtype = "error", + className = await sdbHelper.GetClassNameFromObject(sessionId, object_id, token), + uncaught = caught == 0, + description = exceptionObjectMessage["value"]["value"].Value(), + objectId = $"dotnet:object:{object_id}" + }); + + var ret = await SendCallStack(sessionId, context, reason, thread_id, null, data, args?["callFrames"]?.Values(), token); + return ret; + } + case EventKind.UserBreak: + case EventKind.Step: + case EventKind.Breakpoint: + { + Breakpoint bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == request_id); + string reason = "other";//other means breakpoint + int method_id = 0; + if (event_kind != EventKind.UserBreak) + method_id = ret_debugger_cmd_reader.ReadInt32(); + var ret = await SendCallStack(sessionId, context, reason, thread_id, bp, null, args?["callFrames"]?.Values(), token); + return ret; + } + } + } + return false; + } private async Task LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token) { @@ -826,6 +905,7 @@ private async Task OnResume(MessageId msg_id, CancellationToken token) } //discard managed frames + sdbHelper.ClearCache(); GetContext(msg_id).ClearState(); } @@ -838,12 +918,9 @@ private async Task Step(MessageId msg_id, StepKind kind, CancellationToken if (context.CallStack.Count <= 1 && kind == StepKind.Out) return false; - Result res = await SendMonoCommand(msg_id, MonoCommands.StartSingleStepping(kind), token); - - int? ret_code = res.Value?["result"]?["value"]?.Value(); - - if (ret_code.HasValue && ret_code.Value == 0) - { + var step = await sdbHelper.Step(msg_id, context.ThreadId, kind, token); + if (step == false) { + sdbHelper.ClearCache(); context.ClearState(); await SendCommand(msg_id, "Debugger.stepOut", new JObject(), token); return false; @@ -971,24 +1048,21 @@ internal async Task GetScopeProperties(SessionId msg_id, int scope_id, C return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scope_id}" })); VarInfo[] var_ids = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); - Result res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, var_ids), token); - - //if we fail we just buble that to the IDE (and let it panic over it) - if (res.IsErr) - return res; - JObject[] values = res.Value?["result"]?["value"]?.Values().ToArray(); - - if (values == null || values.Length == 0) - return Result.OkFromObject(new { result = Array.Empty() }); - - PerScopeCache frameCache = ctx.GetCacheForScope(scope_id); - foreach (JObject value in values) + var values = await sdbHelper.StackFrameGetValues(msg_id, scope.Method, ctx.ThreadId, scope_id, var_ids, token); + if (values != null) { - frameCache.Locals[value["name"]?.Value()] = value; - } + if (values == null || values.Count == 0) + return Result.OkFromObject(new { result = Array.Empty() }); - return Result.OkFromObject(new { result = values }); + PerScopeCache frameCache = ctx.GetCacheForScope(scope_id); + foreach (JObject value in values) + { + frameCache.Locals[value["name"]?.Value()] = value; + } + return Result.OkFromObject(new { result = values }); + } + return Result.OkFromObject(new { result = Array.Empty() }); } catch (Exception exception) { @@ -1004,16 +1078,16 @@ private async Task SetMonoBreakpoint(SessionId sessionId, string req int method_token = bp.Location.CliLocation.Method.Token; int il_offset = bp.Location.CliLocation.Offset; - Result res = await SendMonoCommand(sessionId, MonoCommands.SetBreakpoint(asm_name, method_token, il_offset), token); - int? ret_code = res.Value?["result"]?["value"]?.Value(); + var assembly_id = await sdbHelper.GetAssemblyId(sessionId, asm_name, token); + var method_id = await sdbHelper.GetMethodIdByToken(sessionId, assembly_id, method_token, token); + var breakpoint_id = await sdbHelper.SetBreakpoint(sessionId, method_id, il_offset, token); - if (ret_code.HasValue) + if (breakpoint_id > 0) { - bp.RemoteId = ret_code.Value; + bp.RemoteId = breakpoint_id; bp.State = BreakpointState.Active; //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); } - return bp; } @@ -1021,7 +1095,6 @@ private async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, Exe { JObject scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); Log("debug", $"sending {source.Url} {context.Id} {sessionId.sessionId}"); - SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); foreach (var req in context.BreakpointRequests.Values) @@ -1033,7 +1106,7 @@ private async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, Exe } } - private async Task LoadStore(SessionId sessionId, CancellationToken token) + internal async Task LoadStore(SessionId sessionId, CancellationToken token) { ExecutionContext context = GetContext(sessionId); @@ -1072,12 +1145,16 @@ private async Task RuntimeReady(SessionId sessionId, CancellationTok if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) return await context.ready.Task; - Result clear_result = await SendMonoCommand(sessionId, MonoCommands.ClearAllBreakpoints(), token); - if (clear_result.IsErr) + var command_params = new MemoryStream(); + var ret_debugger_cmd_reader = await sdbHelper.SendDebuggerAgentCommand(sessionId, CmdEventRequest.ClearAllBreakpoints, command_params, token); + if (ret_debugger_cmd_reader == null) { - Log("verbose", $"Failed to clear breakpoints due to {clear_result}"); + Log("verbose", $"Failed to clear breakpoints"); } + await sdbHelper.SetProtocolVersion(sessionId, token); + await sdbHelper.EnableReceiveUserBreakRequest(sessionId, token); + DebugStore store = await LoadStore(sessionId, token); context.ready.SetResult(store); @@ -1095,10 +1172,8 @@ private async Task RemoveBreakpoint(MessageId msg_id, JObject args, Cancellation foreach (Breakpoint bp in breakpointRequest.Locations) { - Result res = await SendMonoCommand(msg_id, MonoCommands.RemoveBreakpoint(bp.RemoteId), token); - int? ret_code = res.Value?["result"]?["value"]?.Value(); - - if (ret_code.HasValue) + var breakpoint_removed = await sdbHelper.RemoveBreakpoint(msg_id, bp.RemoteId, token); + if (breakpoint_removed) { bp.RemoteId = -1; bp.State = BreakpointState.Disabled; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs new file mode 100644 index 00000000000000..c296c8501473a5 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -0,0 +1,1779 @@ +// 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.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; + +namespace Microsoft.WebAssembly.Diagnostics +{ + internal enum TokenType + { + MdtModule = 0x00000000, // + MdtTypeRef = 0x01000000, // + MdtTypeDef = 0x02000000, // + MdtFieldDef = 0x04000000, // + MdtMethodDef = 0x06000000, // + MdtParamDef = 0x08000000, // + MdtInterfaceImpl = 0x09000000, // + MdtMemberRef = 0x0a000000, // + MdtCustomAttribute = 0x0c000000, // + MdtPermission = 0x0e000000, // + MdtSignature = 0x11000000, // + MdtEvent = 0x14000000, // + MdtProperty = 0x17000000, // + MdtModuleRef = 0x1a000000, // + MdtTypeSpec = 0x1b000000, // + MdtAssembly = 0x20000000, // + MdtAssemblyRef = 0x23000000, // + MdtFile = 0x26000000, // + MdtExportedType = 0x27000000, // + MdtManifestResource = 0x28000000, // + MdtGenericParam = 0x2a000000, // + MdtMethodSpec = 0x2b000000, // + MdtGenericParamConstraint = 0x2c000000, + MdtString = 0x70000000, // + MdtName = 0x71000000, // + MdtBaseType = 0x72000000, // Leave this on the high end value. This does not correspond to metadata table + } + + internal enum CommandSet { + Vm = 1, + ObjectRef = 9, + StringRef = 10, + Thread = 11, + ArrayRef = 13, + EventRequest = 15, + StackFrame = 16, + AppDomain = 20, + Assembly = 21, + Method = 22, + Type = 23, + Module = 24, + Field = 25, + Event = 64, + Pointer = 65 + } + + internal enum EventKind { + VmStart = 0, + VmDeath = 1, + ThreadStart = 2, + ThreadDeath = 3, + AppDomainCreate = 4, + AppDomainUnload = 5, + MethodEntry = 6, + MethodExit = 7, + AssemblyLoad = 8, + AssemblyUnload = 9, + Breakpoint = 10, + Step = 11, + TypeLoad = 12, + Exception = 13, + KeepAlive = 14, + UserBreak = 15, + UserLog = 16, + Crash = 17 + } + + internal enum ModifierKind { + Count = 1, + ThreadOnly = 3, + LocationOnly = 7, + ExceptionOnly = 8, + Step = 10, + AssemblyOnly = 11, + SourceFileOnly = 12, + TypeNameOnly = 13 + } + + + internal enum SuspendPolicy { + None = 0, + EventThread = 1, + All = 2 + } + + internal enum CmdVM { + Version = 1, + AllThreads = 2, + Suspend = 3, + Resume = 4, + Exit = 5, + Dispose = 6, + InvokeMethod = 7, + SetProtocolVersion = 8, + AbortInvoke = 9, + SetKeepAlive = 10, + GetTypesForSourceFile = 11, + GetTypes = 12, + InvokeMethods = 13, + StartBuffering = 14, + StopBuffering = 15, + VmReadMemory = 16, + VmWriteMemory = 17, + GetAssemblyByName = 18 + } + + internal enum CmdFrame { + GetValues = 1, + GetThis = 2, + SetValues = 3, + GetDomain = 4, + SetThis = 5, + GetArgument = 6, + GetArguments = 7 + } + + internal enum CmdEvent { + Composite = 100 + } + + internal enum CmdThread { + GetFrameInfo = 1, + GetName = 2, + GetState = 3, + GetInfo = 4, + /* FIXME: Merge into GetInfo when the major protocol version is increased */ + GetId = 5, + /* Ditto */ + GetTid = 6, + SetIp = 7, + GetElapsedTime = 8 + } + + internal enum CmdEventRequest { + Set = 1, + Clear = 2, + ClearAllBreakpoints = 3 + } + + internal enum CmdAppDomain { + GetRootDomain = 1, + GetFriendlyName = 2, + GetAssemblies = 3, + GetEntryAssembly = 4, + CreateString = 5, + GetCorLib = 6, + CreateBoxedValue = 7, + CreateByteArray = 8, + } + + internal enum CmdAssembly { + GetLocation = 1, + GetEntryPoint = 2, + GetManifestModule = 3, + GetObject = 4, + GetType = 5, + GetName = 6, + GetDomain = 7, + GetMetadataBlob = 8, + GetIsDynamic = 9, + GetPdbBlob = 10, + GetTypeFromToken = 11, + GetMethodFromToken = 12, + HasDebugInfo = 13, + } + + internal enum CmdModule { + GetInfo = 1, + ApplyChanges = 2, + } + + internal enum CmdPointer{ + GetValue = 1 + } + + internal enum CmdMethod { + GetName = 1, + GetDeclaringType = 2, + GetDebugInfo = 3, + GetParamInfo = 4, + GetLocalsInfo = 5, + GetInfo = 6, + GetBody = 7, + ResolveToken = 8, + GetCattrs = 9, + MakeGenericMethod = 10, + Token = 11, + Assembly = 12, + ClassToken = 13, + AsyncDebugInfo = 14, + GetNameFull = 15 + } + + internal enum CmdType { + GetInfo = 1, + GetMethods = 2, + GetFields = 3, + GetValues = 4, + GetObject = 5, + GetSourceFiles = 6, + SetValues = 7, + IsAssignableFrom = 8, + GetProperties = 9, + GetCattrs = 10, + GetFieldCattrs = 11, + GetPropertyCattrs = 12, + /* FIXME: Merge into GetSourceFiles when the major protocol version is increased */ + GetSourceFiles2 = 13, + /* FIXME: Merge into GetValues when the major protocol version is increased */ + GetValues2 = 14, + GetMethodsByNameFlags = 15, + GetInterfaces = 16, + GetInterfacesMap = 17, + IsInitialized = 18, + CreateInstance = 19, + GetValueSize = 20, + GetValuesICorDbg = 21, + GetParents = 22 + } + + internal enum CmdArray { + GetLength = 1, + GetValues = 2, + SetValues = 3, + RefGetType = 4 + } + + + internal enum CmdField { + GetInfo = 1 + } + + internal enum CmdString { + GetValue = 1, + GetLength = 2, + GetChars = 3 + } + + internal enum CmdObject { + RefGetType = 1, + RefGetValues = 2, + RefIsCollected = 3, + RefGetAddress = 4, + RefGetDomain = 5, + RefSetValues = 6, + RefGetInfo = 7, + GetValuesICorDbg = 8, + RefDelegateGetMethod = 9, + RefIsDelegate = 10 + } + + internal enum ElementType { + End = 0x00, + Void = 0x01, + Boolean = 0x02, + Char = 0x03, + I1 = 0x04, + U1 = 0x05, + I2 = 0x06, + U2 = 0x07, + I4 = 0x08, + U4 = 0x09, + I8 = 0x0a, + U8 = 0x0b, + R4 = 0x0c, + R8 = 0x0d, + String = 0x0e, + Ptr = 0x0f, + ByRef = 0x10, + ValueType = 0x11, + Class = 0x12, + Var = 0x13, + Array = 0x14, + GenericInst = 0x15, + TypedByRef = 0x16, + I = 0x18, + U = 0x19, + FnPtr = 0x1b, + Object = 0x1c, + SzArray = 0x1d, + MVar = 0x1e, + CModReqD = 0x1f, + CModOpt = 0x20, + Internal = 0x21, + Modifier = 0x40, + Sentinel = 0x41, + Pinned = 0x45, + + Type = 0x50, + Boxed = 0x51, + Enum = 0x55 + } + + internal enum ValueTypeId { + Null = 0xf0, + Type = 0xf1, + VType = 0xf2, + FixedArray = 0xf3 + } + internal enum MonoTypeNameFormat{ + FormatIL, + FormatReflection, + FullName, + AssemblyQualified + } + + internal enum StepFilter { + None = 0, + StaticCtor = 1, + DebuggerHidden = 2, + DebuggerStepThrough = 4, + DebuggerNonUserCode = 8 + } + + internal class MonoBinaryReader : BinaryReader + { + public MonoBinaryReader(Stream stream) : base(stream) {} + + internal static unsafe void PutBytesBE (byte *dest, byte *src, int count) + { + int i = 0; + + if (BitConverter.IsLittleEndian){ + dest += count; + for (; i < count; i++) + *(--dest) = *src++; + } else { + for (; i < count; i++) + *dest++ = *src++; + } + } + + public override string ReadString() + { + var valueLen = ReadInt32(); + char[] value = new char[valueLen]; + Read(value, 0, valueLen); + return new string(value); + } + public unsafe long ReadLong() + { + byte[] data = new byte[8]; + Read(data, 0, 8); + + long ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 8); + } + + return ret; + } + public override unsafe sbyte ReadSByte() + { + byte[] data = new byte[4]; + Read(data, 0, 4); + + int ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 4); + } + return (sbyte)ret; + } + + public unsafe byte ReadUByte() + { + byte[] data = new byte[4]; + Read(data, 0, 4); + + int ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 4); + } + return (byte)ret; + } + + public override unsafe int ReadInt32() + { + byte[] data = new byte[4]; + Read(data, 0, 4); + int ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 4); + } + return ret; + } + + public override unsafe double ReadDouble() + { + byte[] data = new byte[8]; + Read(data, 0, 8); + + double ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 8); + } + return ret; + } + + public override unsafe uint ReadUInt32() + { + byte[] data = new byte[4]; + Read(data, 0, 4); + + uint ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 4); + } + return ret; + } + public unsafe ushort ReadUShort() + { + byte[] data = new byte[4]; + Read(data, 0, 4); + + uint ret; + fixed (byte *src = &data[0]){ + PutBytesBE ((byte *) &ret, src, 4); + } + return (ushort)ret; + } + } + + internal class MonoBinaryWriter : BinaryWriter + { + public MonoBinaryWriter(Stream stream) : base(stream) {} + public void WriteString(string val) + { + Write(val.Length); + Write(val.ToCharArray()); + } + public void WriteLong(long val) + { + Write((int)((val >> 32) & 0xffffffff)); + Write((int)((val >> 0) & 0xffffffff)); + } + public override void Write(int val) + { + byte[] bytes = BitConverter.GetBytes(val); + Array.Reverse(bytes, 0, bytes.Length); + Write(bytes); + } + public void WriteObj(DotnetObjectId objectId, MonoSDBHelper sdbHelper) + { + if (objectId.Scheme == "object") + { + Write((byte)ElementType.Class); + Write(int.Parse(objectId.Value)); + } + if (objectId.Scheme == "valuetype") + { + Write(sdbHelper.valueTypes[int.Parse(objectId.Value)].valueTypeBuffer); + } + } + } + internal class FieldTypeClass + { + public int Id { get; } + public string Name { get; } + public int TypeId { get; } + public FieldTypeClass(int id, string name, int typeId) + { + Id = id; + Name = name; + TypeId = typeId; + } + } + internal class ValueTypeClass + { + public byte[] valueTypeBuffer; + public JArray valueTypeJson; + public JArray valueTypeJsonProps; + public int typeId; + public JArray valueTypeProxy; + public string valueTypeVarName; + public bool valueTypeAutoExpand; + public int Id; + public ValueTypeClass(string varName, byte[] buffer, JArray json, int id, bool expand_properties, int valueTypeId) + { + valueTypeBuffer = buffer; + valueTypeJson = json; + typeId = id; + valueTypeJsonProps = null; + valueTypeProxy = null; + valueTypeVarName = varName; + valueTypeAutoExpand = expand_properties; + Id = valueTypeId; + } + } + internal class PointerValue + { + public long address; + public int typeId; + public string varName; + public PointerValue(long address, int typeId, string varName) + { + this.address = address; + this.typeId = typeId; + this.varName = varName; + } + + } + internal class MonoSDBHelper + { + internal Dictionary valueTypes = new Dictionary(); + internal Dictionary pointerValues = new Dictionary(); + private static int debugger_object_id; + private static int cmd_id; + private static int GetId() {return cmd_id++;} + private MonoProxy proxy; + private static int MINOR_VERSION = 61; + private static int MAJOR_VERSION = 2; + public MonoSDBHelper(MonoProxy proxy) + { + this.proxy = proxy; + } + + public void ClearCache() + { + valueTypes = new Dictionary(); + pointerValues = new Dictionary(); + } + + public async Task SetProtocolVersion(SessionId sessionId, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(MAJOR_VERSION); + command_params_writer.Write(MINOR_VERSION); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.SetProtocolVersion, command_params, token); + return true; + } + public async Task EnableReceiveUserBreakRequest(SessionId sessionId, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((byte)EventKind.UserBreak); + command_params_writer.Write((byte)SuspendPolicy.None); + command_params_writer.Write((byte)0); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); + return true; + } + internal async Task SendDebuggerAgentCommandInternal(SessionId sessionId, int command_set, int command, MemoryStream parms, CancellationToken token) + { + Result res = await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommand(GetId(), command_set, command, Convert.ToBase64String(parms.ToArray())), token); + if (res.IsErr) { + throw new Exception($"SendDebuggerAgentCommand Error - {(CommandSet)command_set} - {command}"); + } + byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); + var ret_debugger_cmd = new MemoryStream(newBytes); + var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); + return ret_debugger_cmd_reader; + } + + internal CommandSet GetCommandSetForCommand(T command) => + command switch { + CmdVM => CommandSet.Vm, + CmdObject => CommandSet.ObjectRef, + CmdString => CommandSet.StringRef, + CmdThread => CommandSet.Thread, + CmdArray => CommandSet.ArrayRef, + CmdEventRequest => CommandSet.EventRequest, + CmdFrame => CommandSet.StackFrame, + CmdAppDomain => CommandSet.AppDomain, + CmdAssembly => CommandSet.Assembly, + CmdMethod => CommandSet.Method, + CmdType => CommandSet.Type, + CmdModule => CommandSet.Module, + CmdField => CommandSet.Field, + CmdEvent => CommandSet.Event, + CmdPointer => CommandSet.Pointer, + _ => throw new Exception ("Unknown CommandSet") + }; + + internal Task SendDebuggerAgentCommand(SessionId sessionId, T command, MemoryStream parms, CancellationToken token) where T : Enum => + SendDebuggerAgentCommandInternal(sessionId, (int)GetCommandSetForCommand(command), (int)(object)command, parms, token); + + internal Task SendDebuggerAgentCommandWithParms(SessionId sessionId, T command, MemoryStream parms, int type, string extraParm, CancellationToken token) where T : Enum => + SendDebuggerAgentCommandWithParmsInternal(sessionId, (int)GetCommandSetForCommand(command), (int)(object)command, parms, type, extraParm, token); + + internal async Task SendDebuggerAgentCommandWithParmsInternal(SessionId sessionId, int command_set, int command, MemoryStream parms, int type, string extraParm, CancellationToken token) + { + Result res = await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommandWithParms(GetId(), command_set, command, Convert.ToBase64String(parms.ToArray()), parms.ToArray().Length, type, extraParm), token); + if (res.IsErr) { + throw new Exception("SendDebuggerAgentCommandWithParms Error"); + } + byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); + var ret_debugger_cmd = new MemoryStream(newBytes); + var ret_debugger_cmd_reader = new MonoBinaryReader(ret_debugger_cmd); + return ret_debugger_cmd_reader; + } + + public async Task GetMethodToken(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Token, command_params, token); + return ret_debugger_cmd_reader.ReadInt32() & 0xffffff; //token + } + + public async Task GetMethodIdByToken(SessionId sessionId, int assembly_id, int method_token, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(assembly_id); + command_params_writer.Write(method_token | (int)TokenType.MdtMethodDef); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetMethodFromToken, command_params, token); + return ret_debugger_cmd_reader.ReadInt32(); + } + + public async Task GetAssemblyIdFromMethod(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.Assembly, command_params, token); + return ret_debugger_cmd_reader.ReadInt32(); //assembly_id + } + + public async Task GetAssemblyId(SessionId sessionId, string asm_name, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.WriteString(asm_name); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.GetAssemblyByName, command_params, token); + return ret_debugger_cmd_reader.ReadInt32(); + } + + public async Task GetAssemblyName(SessionId sessionId, int assembly_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(assembly_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetLocation, command_params, token); + return ret_debugger_cmd_reader.ReadString(); + } + + + public async Task GetAssemblyNameFull(SessionId sessionId, int assembly_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(assembly_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdAssembly.GetName, command_params, token); + var name = ret_debugger_cmd_reader.ReadString(); + return name.Remove(name.IndexOf(",")) + ".dll"; + } + + public async Task GetMethodName(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetNameFull, command_params, token); + var methodName = ret_debugger_cmd_reader.ReadString(); + return methodName.Substring(methodName.IndexOf(":")+1); + } + + public async Task MethodIsStatic(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetInfo, command_params, token); + var flags = ret_debugger_cmd_reader.ReadInt32(); + return (flags & 0x0010) > 0; //check method is static + } + + public async Task GetParamCount(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, command_params, token); + ret_debugger_cmd_reader.ReadInt32(); + int param_count = ret_debugger_cmd_reader.ReadInt32(); + return param_count; + } + + public async Task GetReturnType(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, command_params, token); + ret_debugger_cmd_reader.ReadInt32(); + ret_debugger_cmd_reader.ReadInt32(); + ret_debugger_cmd_reader.ReadInt32(); + var retType = ret_debugger_cmd_reader.ReadInt32(); + var ret = await GetTypeName(sessionId, retType, token); + return ret; + } + + public async Task GetParameters(SessionId sessionId, int method_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(method_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetParamInfo, command_params, token); + ret_debugger_cmd_reader.ReadInt32(); + var paramCount = ret_debugger_cmd_reader.ReadInt32(); + ret_debugger_cmd_reader.ReadInt32(); + var retType = ret_debugger_cmd_reader.ReadInt32(); + var parameters = "("; + for (int i = 0 ; i < paramCount; i++) + { + var paramType = ret_debugger_cmd_reader.ReadInt32(); + parameters += await GetTypeName(sessionId, paramType, token); + parameters = parameters.Replace("System.Func", "Func"); + if (i + 1 < paramCount) + parameters += ","; + } + parameters += ")"; + return parameters; + } + + public async Task SetBreakpoint(SessionId sessionId, int method_id, long il_offset, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((byte)EventKind.Breakpoint); + command_params_writer.Write((byte)SuspendPolicy.None); + command_params_writer.Write((byte)1); + command_params_writer.Write((byte)ModifierKind.LocationOnly); + command_params_writer.Write(method_id); + command_params_writer.WriteLong(il_offset); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); + return ret_debugger_cmd_reader.ReadInt32(); + } + + public async Task RemoveBreakpoint(SessionId sessionId, int breakpoint_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((byte)EventKind.Breakpoint); + command_params_writer.Write((int) breakpoint_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Clear, command_params, token); + + if (ret_debugger_cmd_reader != null) + return true; + return false; + } + + public async Task Step(SessionId sessionId, int thread_id, StepKind kind, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((byte)EventKind.Step); + command_params_writer.Write((byte)SuspendPolicy.None); + command_params_writer.Write((byte)1); + command_params_writer.Write((byte)ModifierKind.Step); + command_params_writer.Write(thread_id); + command_params_writer.Write((int)0); + command_params_writer.Write((int)kind); + command_params_writer.Write((int)(StepFilter.StaticCtor | StepFilter.DebuggerHidden)); //filter + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); + if (ret_debugger_cmd_reader == null) + return false; + var isBPOnManagedCode = ret_debugger_cmd_reader.ReadInt32(); + if (isBPOnManagedCode == 0) + return false; + return true; + } + + public async Task ClearSingleStep(SessionId sessionId, int req_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((byte)EventKind.Step); + command_params_writer.Write((int) req_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Clear, command_params, token); + + if (ret_debugger_cmd_reader != null) + return true; + return false; + } + + public async Task> GetTypeFields(SessionId sessionId, int type_id, CancellationToken token) + { + var ret = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(type_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetFields, command_params, token); + var nFields = ret_debugger_cmd_reader.ReadInt32(); + + for (int i = 0 ; i < nFields; i++) + { + int fieldId = ret_debugger_cmd_reader.ReadInt32(); //fieldId + string fieldNameStr = ret_debugger_cmd_reader.ReadString(); + int typeId = ret_debugger_cmd_reader.ReadInt32(); //typeId + ret_debugger_cmd_reader.ReadInt32(); //attrs + if (fieldNameStr.Contains("k__BackingField")) + { + fieldNameStr = fieldNameStr.Replace("k__BackingField", ""); + fieldNameStr = fieldNameStr.Replace("<", ""); + fieldNameStr = fieldNameStr.Replace(">", ""); + } + ret.Add(new FieldTypeClass(fieldId, fieldNameStr, typeId)); + } + return ret; + } + public string ReplaceCommonClassNames(string className) + { + className = className.Replace("System.String", "string"); + className = className.Replace("System.Boolean", "bool"); + className = className.Replace("System.Char", "char"); + className = className.Replace("System.Int32", "int"); + className = className.Replace("System.Object", "object"); + className = className.Replace("System.Void", "void"); + className = className.Replace("System.Byte", "byte"); + return className; + } + public async Task GetTypeName(SessionId sessionId, int type_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(type_id); + command_params_writer.Write((int) MonoTypeNameFormat.FormatReflection); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetInfo, command_params, token); + + ret_debugger_cmd_reader.ReadString(); + + ret_debugger_cmd_reader.ReadString(); + + string className = ret_debugger_cmd_reader.ReadString(); + + className = className.Replace("+", "."); + className = Regex.Replace(className, @"`\d+", ""); + className = className.Replace("[]", "__SQUARED_BRACKETS__"); + className = className.Replace("[", "<"); + className = className.Replace("]", ">"); + className = className.Replace("__SQUARED_BRACKETS__", "[]"); + className = className.Replace(",", ", "); + className = ReplaceCommonClassNames(className); + return className; + } + + public async Task GetStringValue(SessionId sessionId, int string_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(string_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdString.GetValue, command_params, token); + var isUtf16 = ret_debugger_cmd_reader.ReadByte(); + if (isUtf16 == 0) { + return ret_debugger_cmd_reader.ReadString(); + } + return null; + } + public async Task GetArrayLength(SessionId sessionId, int object_id, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(object_id); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdArray.GetLength, command_params, token); + var length = ret_debugger_cmd_reader.ReadInt32(); + length = ret_debugger_cmd_reader.ReadInt32(); + return length; + } + public async Task> GetTypeIdFromObject(SessionId sessionId, int object_id, bool withParents, CancellationToken token) + { + List ret = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(object_id); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetType, command_params, token); + var type_id = ret_debugger_cmd_reader.ReadInt32(); + ret.Add(type_id); + if (withParents) + { + command_params = new MemoryStream(); + command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(type_id); + ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetParents, command_params, token); + var parentsCount = ret_debugger_cmd_reader.ReadInt32(); + for (int i = 0 ; i < parentsCount; i++) + { + ret.Add(ret_debugger_cmd_reader.ReadInt32()); + } + } + return ret; + } + + public async Task GetClassNameFromObject(SessionId sessionId, int object_id, CancellationToken token) + { + var type_id = await GetTypeIdFromObject(sessionId, object_id, false, token); + return await GetTypeName(sessionId, type_id[0], token); + } + + public async Task GetMethodIdByName(SessionId sessionId, int type_id, string method_name, CancellationToken token) + { + var ret = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((int)type_id); + command_params_writer.WriteString(method_name); + command_params_writer.Write((int)(0x10 | 4)); //instance methods + command_params_writer.Write((int)1); //case sensitive + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetMethodsByNameFlags, command_params, token); + var nMethods = ret_debugger_cmd_reader.ReadInt32(); + return ret_debugger_cmd_reader.ReadInt32(); + } + + public async Task IsDelegate(SessionId sessionId, int objectId, CancellationToken token) + { + var ret = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((int)objectId); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefIsDelegate, command_params, token); + return ret_debugger_cmd_reader.ReadByte() == 1; + } + + public async Task GetDelegateMethod(SessionId sessionId, int objectId, CancellationToken token) + { + var ret = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((int)objectId); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefDelegateGetMethod, command_params, token); + return ret_debugger_cmd_reader.ReadInt32(); + } + + public async Task GetDelegateMethodDescription(SessionId sessionId, int objectId, CancellationToken token) + { + var methodId = await GetDelegateMethod(sessionId, objectId, token); + + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(methodId); + //Console.WriteLine("methodId - " + methodId); + if (methodId == 0) + return ""; + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.GetName, command_params, token); + var methodName = ret_debugger_cmd_reader.ReadString(); + + var returnType = await GetReturnType(sessionId, methodId, token); + var parameters = await GetParameters(sessionId, methodId, token); + + return $"{returnType} {methodName} {parameters}"; + } + public async Task InvokeMethod(SessionId sessionId, byte[] valueTypeBuffer, int method_id, string varName, CancellationToken token) + { + MemoryStream parms = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(parms); + command_params_writer.Write(method_id); + command_params_writer.Write(valueTypeBuffer); + command_params_writer.Write(0); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdVM.InvokeMethod, parms, token); + ret_debugger_cmd_reader.ReadByte(); //number of objects returned. + return await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, varName, false, -1, token); + } + public async Task CreateJArrayForProperties(SessionId sessionId, int typeId, byte[] object_buffer, JArray attributes, bool isAutoExpandable, string objectId, bool isOwn, CancellationToken token) + { + JArray ret = new JArray(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(typeId); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, command_params, token); + var nProperties = ret_debugger_cmd_reader.ReadInt32(); + for (int i = 0 ; i < nProperties; i++) + { + ret_debugger_cmd_reader.ReadInt32(); //propertyId + string propertyNameStr = ret_debugger_cmd_reader.ReadString(); + var getMethodId = ret_debugger_cmd_reader.ReadInt32(); + ret_debugger_cmd_reader.ReadInt32(); //setmethod + var attrs = ret_debugger_cmd_reader.ReadInt32(); //attrs + if (getMethodId == 0 || await GetParamCount(sessionId, getMethodId, token) != 0 || await MethodIsStatic(sessionId, getMethodId, token)) + continue; + JObject propRet = null; + if (attributes.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).Any()) + continue; + if (isAutoExpandable) + { + try { + propRet = await InvokeMethod(sessionId, object_buffer, getMethodId, propertyNameStr, token); + } + catch (Exception) + { + continue; + } + } + else + { + propRet = JObject.FromObject(new { + get = new + { + type = "function", + objectId = $"{objectId}:method_id:{getMethodId}", + className = "Function", + description = "get " + propertyNameStr + " ()", + methodId = getMethodId, + objectIdValue = objectId + }, + name = propertyNameStr + }); + } + if (isOwn) + propRet["isOwn"] = true; + ret.Add(propRet); + } + return ret; + } + public async Task GetPointerContent(SessionId sessionId, int pointerId, CancellationToken token) + { + var ret = new List(); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.WriteLong(pointerValues[pointerId].address); + command_params_writer.Write(pointerValues[pointerId].typeId); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdPointer.GetValue, command_params, token); + var varName = pointerValues[pointerId].varName; + if (int.TryParse(varName, out _)) + varName = $"[{varName}]"; + return await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, "*" + varName, false, -1, token); + } + public async Task GetPropertiesValuesOfValueType(SessionId sessionId, int valueTypeId, CancellationToken token) + { + JArray ret = new JArray(); + var valueType = valueTypes[valueTypeId]; + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(valueType.typeId); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetParents, command_params, token); + var parentsCount = ret_debugger_cmd_reader.ReadInt32(); + List typesToGetProperties = new List(); + typesToGetProperties.Add(valueType.typeId); + for (int i = 0 ; i < parentsCount; i++) + { + typesToGetProperties.Add(ret_debugger_cmd_reader.ReadInt32()); + } + for (int i = 0 ; i < typesToGetProperties.Count; i++) + { + var properties = await CreateJArrayForProperties(sessionId, typesToGetProperties[i], valueType.valueTypeBuffer, valueType.valueTypeJson, valueType.valueTypeAutoExpand, $"dotnet:valuetype:{valueType.Id}", i == 0, token); + ret = new JArray(ret.Union(properties)); + } + + return ret; + } + + public bool AutoExpandable(string className) { + if (className == "System.DateTime" || + className == "System.DateTimeOffset" || + className == "System.TimeSpan") + return true; + return false; + } + + public bool AutoInvokeToString(string className) { + if (className == "System.DateTime" || + className == "System.DateTimeOffset" || + className == "System.TimeSpan" || + className == "System.Decimal" || + className == "System.Guid") + return true; + return false; + } + + public JObject CreateJObject(T value, string type, string description, bool writable, string className = null, string objectId = null, string __custom_type = null, string subtype = null, bool isValueType = false, bool expanded = false, bool isEnum = false) + { + var ret = JObject.FromObject(new { + value = new + { + type, + value, + description + }, + writable + }); + if (__custom_type != null) + ret["value"]["__custom_type"] = __custom_type; + if (className != null) + ret["value"]["className"] = className; + if (objectId != null) + ret["value"]["objectId"] = objectId; + if (subtype != null) + ret["value"]["subtype"] = subtype; + if (isValueType) + ret["value"]["isValueType"] = isValueType; + if (expanded) + ret["value"]["expanded"] = expanded; + if (isEnum) + ret["value"]["isEnum"] = isEnum; + return ret; + + } + public JObject CreateJObjectForBoolean(int value) + { + return CreateJObject(value == 0 ? false : true, "boolean", value == 0 ? "false" : "true", true); + } + + public JObject CreateJObjectForNumber(T value) + { + return CreateJObject(value, "number", value.ToString(), true); + } + + public JObject CreateJObjectForChar(int value) + { + var description = $"{value.ToString()} '{Convert.ToChar(value)}'"; + return CreateJObject(description, "symbol", description, true); + } + + public async Task CreateJObjectForPtr(SessionId sessionId, ElementType etype, MonoBinaryReader ret_debugger_cmd_reader, string name, CancellationToken token) + { + string type; + string value; + long valueAddress = ret_debugger_cmd_reader.ReadLong(); + var typeId = ret_debugger_cmd_reader.ReadInt32(); + var className = ""; + if (etype == ElementType.FnPtr) + className = "(*())"; //to keep the old behavior + else + className = "(" + await GetTypeName(sessionId, typeId, token) + ")"; + + int pointerId = 0; + if (valueAddress != 0 && className != "(void*)") + { + pointerId = Interlocked.Increment(ref debugger_object_id); + type = "object"; + value = className; + pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name); + } + else + { + type = "symbol"; + value = className + " " + valueAddress; + } + return CreateJObject(value, type, value, false, className, $"dotnet:pointer:{pointerId}", "pointer"); + } + + public async Task CreateJObjectForString(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + { + var string_id = ret_debugger_cmd_reader.ReadInt32(); + var value = await GetStringValue(sessionId, string_id, token); + return CreateJObject(value, "string", value, false); + } + + public async Task CreateJObjectForArray(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + { + var objectId = ret_debugger_cmd_reader.ReadInt32(); + var value = await GetClassNameFromObject(sessionId, objectId, token); + var length = await GetArrayLength(sessionId, objectId, token); + return CreateJObject(null, "object", $"{value.ToString()}({length})", false, value.ToString(), "dotnet:array:" + objectId, null, "array"); + } + + public async Task CreateJObjectForObject(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, int typeIdFromAttribute, CancellationToken token) + { + var objectId = ret_debugger_cmd_reader.ReadInt32(); + var className = ""; + var type_id = await GetTypeIdFromObject(sessionId, objectId, false, token); + className = await GetTypeName(sessionId, type_id[0], token); + var description = className.ToString(); + if (await IsDelegate(sessionId, objectId, token)) + { + if (typeIdFromAttribute != -1) + { + className = await GetTypeName(sessionId, typeIdFromAttribute, token); + } + + description = await GetDelegateMethodDescription(sessionId, objectId, token); + if (description == "") + { + return CreateJObject(className.ToString(), "symbol", className.ToString(), false); + } + } + return CreateJObject(null, "object", description, false, className, $"dotnet:object:{objectId}"); + } + + public async Task CreateJObjectForValueType(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, string name, long initialPos, CancellationToken token) + { + JObject fieldValueType = null; + var isEnum = ret_debugger_cmd_reader.ReadByte(); + var isBoxed = ret_debugger_cmd_reader.ReadByte() == 1; + var typeId = ret_debugger_cmd_reader.ReadInt32(); + var className = await GetTypeName(sessionId, typeId, token); + var description = className; + var numFields = ret_debugger_cmd_reader.ReadInt32(); + var fields = await GetTypeFields(sessionId, typeId, token); + JArray valueTypeFields = new JArray(); + if (className.IndexOf("System.Nullable<") == 0) //should we call something on debugger-agent to check??? + { + ret_debugger_cmd_reader.ReadByte(); //ignoring the boolean type + var isNull = ret_debugger_cmd_reader.ReadInt32(); + var value = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, name, false, -1, token); + if (isNull != 0) + return value; + else + return CreateJObject(null, "object", className, false, className, null, null, "null", true); + } + for (int i = 0; i < numFields ; i++) + { + fieldValueType = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, token); + valueTypeFields.Add(fieldValueType); + } + + long endPos = ret_debugger_cmd_reader.BaseStream.Position; + var valueTypeId = Interlocked.Increment(ref debugger_object_id); + + ret_debugger_cmd_reader.BaseStream.Position = initialPos; + byte[] valueTypeBuffer = new byte[endPos - initialPos]; + ret_debugger_cmd_reader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); + ret_debugger_cmd_reader.BaseStream.Position = endPos; + valueTypes[valueTypeId] = new ValueTypeClass(name, valueTypeBuffer, valueTypeFields, typeId, AutoExpandable(className), valueTypeId); + if (AutoInvokeToString(className) || isEnum == 1) { + int method_id = await GetMethodIdByName(sessionId, typeId, "ToString", token); + var retMethod = await InvokeMethod(sessionId, valueTypeBuffer, method_id, "methodRet", token); + description = retMethod["value"]?["value"].Value(); + if (className.Equals("System.Guid")) + description = description.ToUpper(); //to keep the old behavior + } + else if (isBoxed && numFields == 1) { + return fieldValueType; + } + return CreateJObject(null, "object", description, false, className, $"dotnet:valuetype:{valueTypeId}", null, null, true, true, isEnum == 1); + } + + public async Task CreateJObjectForNull(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, CancellationToken token) + { + string className = ""; + ElementType variableType = (ElementType)ret_debugger_cmd_reader.ReadByte(); + switch (variableType) + { + case ElementType.String: + case ElementType.Class: + { + var type_id = ret_debugger_cmd_reader.ReadInt32(); + className = await GetTypeName(sessionId, type_id, token); + break; + + } + case ElementType.SzArray: + case ElementType.Array: + { + ElementType byte_type = (ElementType)ret_debugger_cmd_reader.ReadByte(); + var rank = ret_debugger_cmd_reader.ReadInt32(); + if (byte_type == ElementType.Class) { + var internal_type_id = ret_debugger_cmd_reader.ReadInt32(); + } + var type_id = ret_debugger_cmd_reader.ReadInt32(); + className = await GetTypeName(sessionId, type_id, token); + break; + } + default: + { + var type_id = ret_debugger_cmd_reader.ReadInt32(); + className = await GetTypeName(sessionId, type_id, token); + break; + } + } + return CreateJObject(null, "object", className, false, className, null, null, "null"); + } + + public async Task CreateJObjectForVariableValue(SessionId sessionId, MonoBinaryReader ret_debugger_cmd_reader, string name, bool isOwn, int typeIdFromAttribute, CancellationToken token) + { + long initialPos = ret_debugger_cmd_reader == null ? 0 : ret_debugger_cmd_reader.BaseStream.Position; + ElementType etype = (ElementType)ret_debugger_cmd_reader.ReadByte(); + JObject ret = null; + switch (etype) { + case ElementType.I: + case ElementType.U: + case ElementType.Void: + case (ElementType)ValueTypeId.Type: + case (ElementType)ValueTypeId.VType: + case (ElementType)ValueTypeId.FixedArray: + ret = new JObject{{"Type", "void"}}; + break; + case ElementType.Boolean: + { + var value = ret_debugger_cmd_reader.ReadInt32(); + ret = CreateJObjectForBoolean(value); + break; + } + case ElementType.I1: + { + var value = ret_debugger_cmd_reader.ReadSByte(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.I2: + case ElementType.I4: + { + var value = ret_debugger_cmd_reader.ReadInt32(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.U1: + { + var value = ret_debugger_cmd_reader.ReadUByte(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.U2: + { + var value = ret_debugger_cmd_reader.ReadUShort(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.U4: + { + var value = ret_debugger_cmd_reader.ReadUInt32(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.R4: + { + float value = BitConverter.Int32BitsToSingle(ret_debugger_cmd_reader.ReadInt32()); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.Char: + { + var value = ret_debugger_cmd_reader.ReadInt32(); + ret = CreateJObjectForChar(value); + break; + } + case ElementType.I8: + { + long value = ret_debugger_cmd_reader.ReadLong(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.U8: + { + ulong high = (ulong) ret_debugger_cmd_reader.ReadInt32(); + ulong low = (ulong) ret_debugger_cmd_reader.ReadInt32(); + var value = ((high << 32) | low); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.R8: + { + double value = ret_debugger_cmd_reader.ReadDouble(); + ret = CreateJObjectForNumber(value); + break; + } + case ElementType.FnPtr: + case ElementType.Ptr: + { + ret = await CreateJObjectForPtr(sessionId, etype, ret_debugger_cmd_reader, name, token); + break; + } + case ElementType.String: + { + ret = await CreateJObjectForString(sessionId, ret_debugger_cmd_reader, token); + break; + } + case ElementType.SzArray: + case ElementType.Array: + { + ret = await CreateJObjectForArray(sessionId, ret_debugger_cmd_reader, token); + break; + } + case ElementType.Class: + case ElementType.Object: + { + ret = await CreateJObjectForObject(sessionId, ret_debugger_cmd_reader, typeIdFromAttribute, token); + break; + } + case ElementType.ValueType: + { + ret = await CreateJObjectForValueType(sessionId, ret_debugger_cmd_reader, name, initialPos, token); + break; + } + case (ElementType)ValueTypeId.Null: + { + ret = await CreateJObjectForNull(sessionId, ret_debugger_cmd_reader, token); + break; + } + } + if (isOwn) + ret["isOwn"] = true; + ret["name"] = name; + return ret; + } + + public async Task IsAsyncMethod(SessionId sessionId, int methodId, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(methodId); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdMethod.AsyncDebugInfo, command_params, token); + return ret_debugger_cmd_reader.ReadByte() == 1 ; //token + } + + public async Task StackFrameGetValues(SessionId sessionId, MethodInfo method, int thread_id, int frame_id, VarInfo[] var_ids, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + MonoBinaryReader ret_debugger_cmd_reader = null; + command_params_writer.Write(thread_id); + command_params_writer.Write(frame_id); + command_params_writer.Write(var_ids.Length); + foreach (var var in var_ids) + { + command_params_writer.Write(var.Index); + } + + if (await IsAsyncMethod(sessionId, method.DebuggerId, token)) + { + ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, command_params, token); + ret_debugger_cmd_reader.ReadByte(); //ignore type + var objectId = ret_debugger_cmd_reader.ReadInt32(); + var asyncLocals = await GetObjectValues(sessionId, objectId, true, false, false, false, token); + asyncLocals = new JArray(asyncLocals.Where( asyncLocal => !asyncLocal["name"].Value().Contains("<>") || asyncLocal["name"].Value().EndsWith("__this"))); + foreach (var asyncLocal in asyncLocals) + { + if (asyncLocal["name"].Value().EndsWith("__this")) + asyncLocal["name"] = "this"; + else if (asyncLocal["name"].Value().Contains("<")) + asyncLocal["name"] = Regex.Match(asyncLocal["name"].Value(), @"\<([^)]*)\>").Groups[1].Value; + } + return asyncLocals; + } + + JArray locals = new JArray(); + ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, command_params, token); + foreach (var var in var_ids) + { + var var_json = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, var.Name, false, -1, token); + locals.Add(var_json); + } + if (!method.IsStatic()) + { + ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, command_params, token); + var var_json = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, "this", false, -1, token); + var_json.Add("fieldOffset", -1); + locals.Add(var_json); + } + return locals; + + } + + public async Task GetValueTypeValues(SessionId sessionId, int valueTypeId, bool accessorPropertiesOnly, CancellationToken token) + { + if (valueTypes[valueTypeId].valueTypeJsonProps == null) + { + valueTypes[valueTypeId].valueTypeJsonProps = await GetPropertiesValuesOfValueType(sessionId, valueTypeId, token); + } + if (accessorPropertiesOnly) + return valueTypes[valueTypeId].valueTypeJsonProps; + var ret = new JArray(valueTypes[valueTypeId].valueTypeJson.Union(valueTypes[valueTypeId].valueTypeJsonProps)); + return ret; + } + + public async Task GetValueTypeProxy(SessionId sessionId, int valueTypeId, CancellationToken token) + { + if (valueTypes[valueTypeId].valueTypeProxy != null) + return valueTypes[valueTypeId].valueTypeProxy; + valueTypes[valueTypeId].valueTypeProxy = new JArray(valueTypes[valueTypeId].valueTypeJson); + + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(valueTypes[valueTypeId].typeId); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, command_params, token); + var nProperties = ret_debugger_cmd_reader.ReadInt32(); + + for (int i = 0 ; i < nProperties; i++) + { + ret_debugger_cmd_reader.ReadInt32(); //propertyId + string propertyNameStr = ret_debugger_cmd_reader.ReadString(); + + var getMethodId = ret_debugger_cmd_reader.ReadInt32(); + ret_debugger_cmd_reader.ReadInt32(); //setmethod + ret_debugger_cmd_reader.ReadInt32(); //attrs + if (await MethodIsStatic(sessionId, getMethodId, token)) + continue; + var command_params_to_proxy = new MemoryStream(); + var command_params_writer_to_proxy = new MonoBinaryWriter(command_params_to_proxy); + command_params_writer_to_proxy.Write(getMethodId); + command_params_writer_to_proxy.Write(valueTypes[valueTypeId].valueTypeBuffer); + command_params_writer_to_proxy.Write(0); + valueTypes[valueTypeId].valueTypeProxy.Add(JObject.FromObject(new { + get = JObject.FromObject(new { + commandSet = CommandSet.Vm, + command = CmdVM.InvokeMethod, + buffer = Convert.ToBase64String(command_params_to_proxy.ToArray()), + length = command_params_to_proxy.ToArray().Length + }), + name = propertyNameStr + })); + } + return valueTypes[valueTypeId].valueTypeProxy; + } + + public async Task GetArrayValues(SessionId sessionId, int arrayId, CancellationToken token) + { + var length = await GetArrayLength(sessionId, arrayId, token); + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(arrayId); + command_params_writer.Write(0); + command_params_writer.Write(length); + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdArray.GetValues, command_params, token); + JArray array = new JArray(); + for (int i = 0 ; i < length ; i++) + { + var var_json = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, i.ToString(), false, -1, token); + array.Add(var_json); + } + return array; + } + public async Task EnableExceptions(SessionId sessionId, string state, CancellationToken token) + { + + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write((byte)EventKind.Exception); + command_params_writer.Write((byte)SuspendPolicy.None); + command_params_writer.Write((byte)1); + command_params_writer.Write((byte)ModifierKind.ExceptionOnly); + command_params_writer.Write(0); //exc_class + if (state == "all") + command_params_writer.Write((byte)1); //caught + else + command_params_writer.Write((byte)0); //caught + if (state == "uncaught" || state == "all") + command_params_writer.Write((byte)1); //uncaught + else + command_params_writer.Write((byte)0); //uncaught + command_params_writer.Write((byte)1);//subclasses + command_params_writer.Write((byte)0);//not_filtered_feature + command_params_writer.Write((byte)0);//everything_else + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdEventRequest.Set, command_params, token); + return true; + } + public async Task GetObjectValues(SessionId sessionId, int objectId, bool withProperties, bool withSetter, bool accessorPropertiesOnly, bool ownProperties, CancellationToken token) + { + var typeId = await GetTypeIdFromObject(sessionId, objectId, true, token); + var className = await GetTypeName(sessionId, typeId[0], token); + JArray ret = new JArray(); + if (await IsDelegate(sessionId, objectId, token)) + { + var description = await GetDelegateMethodDescription(sessionId, objectId, token); + + var obj = JObject.FromObject(new { + value = new + { + type = "symbol", + value = description, + description + }, + name = "Target" + }); + ret.Add(obj); + return ret; + } + for (int i = 0; i < typeId.Count; i++) + { + if (!accessorPropertiesOnly) + { + var fields = await GetTypeFields(sessionId, typeId[i], token); + JArray objectFields = new JArray(); + + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(objectId); + command_params_writer.Write(fields.Count); + foreach (var field in fields) + { + command_params_writer.Write(field.Id); + } + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetValues, command_params, token); + + foreach (var field in fields) + { + long initialPos = ret_debugger_cmd_reader.BaseStream.Position; + int valtype = ret_debugger_cmd_reader.ReadByte(); + ret_debugger_cmd_reader.BaseStream.Position = initialPos; + var fieldValue = await CreateJObjectForVariableValue(sessionId, ret_debugger_cmd_reader, field.Name, i == 0, field.TypeId, token); + + if (ret.Where(attribute => attribute["name"].Value().Equals(fieldValue["name"].Value())).Any()) { + continue; + } + if (withSetter) + { + var command_params_to_set = new MemoryStream(); + var command_params_writer_to_set = new MonoBinaryWriter(command_params_to_set); + command_params_writer_to_set.Write(objectId); + command_params_writer_to_set.Write(1); + command_params_writer_to_set.Write(field.Id); + + fieldValue.Add("set", JObject.FromObject(new { + commandSet = CommandSet.ObjectRef, + command = CmdObject.RefSetValues, + buffer = Convert.ToBase64String(command_params_to_set.ToArray()), + valtype, + length = command_params_to_set.ToArray().Length + })); + } + objectFields.Add(fieldValue); + } + ret = new JArray(ret.Union(objectFields)); + } + if (!withProperties) + return ret; + var command_params_obj = new MemoryStream(); + var command_params_obj_writer = new MonoBinaryWriter(command_params_obj); + command_params_obj_writer.WriteObj(new DotnetObjectId("object", $"{objectId}"), this); + var props = await CreateJArrayForProperties(sessionId, typeId[i], command_params_obj.ToArray(), ret, false, $"dotnet:object:{objectId}", i == 0, token); + ret = new JArray(ret.Union(props)); + + // ownProperties + // Note: ownProperties should mean that we return members of the klass itself, + // but we are going to ignore that here, because otherwise vscode/chrome don't + // seem to ask for inherited fields at all. + //if (ownProperties) + //break; + /*if (accessorPropertiesOnly) + break;*/ + } + if (accessorPropertiesOnly) + { + var retAfterRemove = new JArray(); + List> allFields = new List>(); + for (int i = 0; i < typeId.Count; i++) + { + var fields = await GetTypeFields(sessionId, typeId[i], token); + allFields.Add(fields); + } + foreach (var item in ret) + { + bool foundField = false; + for (int j = 0 ; j < allFields.Count; j++) + { + foreach (var field in allFields[j]) + { + if (field.Name.Equals(item["name"].Value())) { + if (item["isOwn"] == null || (item["isOwn"].Value() && j == 0) || !item["isOwn"].Value()) + foundField = true; + break; + } + } + if (foundField) + break; + } + if (!foundField) { + retAfterRemove.Add(item); + } + } + ret = retAfterRemove; + } + return ret; + } + + public async Task GetObjectProxy(SessionId sessionId, int objectId, CancellationToken token) + { + var ret = await GetObjectValues(sessionId, objectId, false, true, false, false, token); + var typeIds = await GetTypeIdFromObject(sessionId, objectId, true, token); + foreach (var typeId in typeIds) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + command_params_writer.Write(typeId); + + var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, command_params, token); + var nProperties = ret_debugger_cmd_reader.ReadInt32(); + for (int i = 0 ; i < nProperties; i++) + { + ret_debugger_cmd_reader.ReadInt32(); //propertyId + string propertyNameStr = ret_debugger_cmd_reader.ReadString(); + var getMethodId = ret_debugger_cmd_reader.ReadInt32(); + var setMethodId = ret_debugger_cmd_reader.ReadInt32(); //setmethod + var attrValue = ret_debugger_cmd_reader.ReadInt32(); //attrs + //Console.WriteLine($"{propertyNameStr} - {attrValue}"); + if (ret.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).Any()) + { + var attr = ret.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).First(); + + var command_params_to_set = new MemoryStream(); + var command_params_writer_to_set = new MonoBinaryWriter(command_params_to_set); + command_params_writer_to_set.Write(setMethodId); + command_params_writer_to_set.Write((byte)ElementType.Class); + command_params_writer_to_set.Write(objectId); + command_params_writer_to_set.Write(1); + if (attr["set"] != null) + { + attr["set"] = JObject.FromObject(new { + commandSet = CommandSet.Vm, + command = CmdVM.InvokeMethod, + buffer = Convert.ToBase64String(command_params_to_set.ToArray()), + valtype = attr["set"]["valtype"], + length = command_params_to_set.ToArray().Length + }); + } + continue; + } + else + { + var command_params_to_get = new MemoryStream(); + var command_params_writer_to_get = new MonoBinaryWriter(command_params_to_get); + command_params_writer_to_get.Write(getMethodId); + command_params_writer_to_get.Write((byte)ElementType.Class); + command_params_writer_to_get.Write(objectId); + command_params_writer_to_get.Write(0); + + ret.Add(JObject.FromObject(new { + get = JObject.FromObject(new { + commandSet = CommandSet.Vm, + command = CmdVM.InvokeMethod, + buffer = Convert.ToBase64String(command_params_to_get.ToArray()), + length = command_params_to_get.ToArray().Length + }), + name = propertyNameStr + })); + } + if (await MethodIsStatic(sessionId, getMethodId, token)) + continue; + } + } + return ret; + } + + public async Task SetVariableValue(SessionId sessionId, int thread_id, int frame_id, int varId, string newValue, CancellationToken token) + { + var command_params = new MemoryStream(); + var command_params_writer = new MonoBinaryWriter(command_params); + MonoBinaryReader ret_debugger_cmd_reader = null; + command_params_writer.Write(thread_id); + command_params_writer.Write(frame_id); + command_params_writer.Write(1); + command_params_writer.Write(varId); + JArray locals = new JArray(); + ret_debugger_cmd_reader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, command_params, token); + int etype = ret_debugger_cmd_reader.ReadByte(); + try + { + ret_debugger_cmd_reader = await SendDebuggerAgentCommandWithParms(sessionId, CmdFrame.SetValues, command_params, etype, newValue, token); + } + catch (Exception) + { + return false; + } + + return true; + } + } +} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs index 00f917c8dc5731..1afa97939d5ef1 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/AssignmentTests.cs @@ -33,7 +33,7 @@ public class AssignmentTests : DebuggerTestBase { "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_R4", TNumber(0), TNumber("3.1415") }, //this is also the value that we see if we debug using VS { "MONO_TYPE_R8", TNumber(0), TNumber("3.1415") }, }; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 9b41983812074b..3d9106959c812e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -706,7 +706,6 @@ internal async Task CheckValue(JToken actual_val, JToken exp_val, string label) var exp_val_str = jp.Value.Value(); bool null_or_empty_exp_val = String.IsNullOrEmpty(exp_val_str); - var actual_field_val = actual_val?.Values()?.FirstOrDefault(a_jp => a_jp.Name == jp.Name); var actual_field_val_str = actual_field_val?.Value?.Value(); if (null_or_empty_exp_val && String.IsNullOrEmpty(actual_field_val_str)) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs index e2b796062e3086..4054c6a2cea11f 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs @@ -13,85 +13,6 @@ namespace DebuggerTests { public class MonoJsTests : DebuggerTestBase { - [Fact] - public async Task FixupNameValueObjectsWithMissingParts() - { - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - - var names = new JObject[] - { - JObject.FromObject(new { name = "Abc" }), - JObject.FromObject(new { name = "Def" }), - JObject.FromObject(new { name = "Xyz" }) - }; - - var values = new JObject[] - { - JObject.FromObject(new { value = TObject("testclass") }), - JObject.FromObject(new { value = TString("test string") }), - }; - - var getters = new JObject[] - { - GetterRes("xyz"), - GetterRes("unattached") - }; - - var list = new[] { names[0], names[1], values[0], names[2], getters[0], getters[1] }; - var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression = $"MONO._fixup_name_value_objects({JsonConvert.SerializeObject(list)})", returnByValue = true }), token); - Assert.True(res.IsOk); - - await CheckProps(res.Value["result"]["value"], new - { - Abc = TSymbol(""), - Def = TObject("testclass"), - Xyz = TGetter("xyz") - }, "#1", num_fields: 4); - - JObject.DeepEquals(getters[1], res.Value["result"]["value"].Values().ToArray()[3]); - - static JObject GetterRes(string name) => JObject.FromObject(new - { - get = new - { - className = "Function", - description = $"get {name} () {{}}", - type = "function" - } - }); - } - - [Fact] - public async Task GetParamsAndLocalsWithInvalidIndices() - { - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", - null, -1, -1, "IntAdd"); - - var scope_id = pause_location["callFrames"][0]["callFrameId"].Value(); - var scope = int.Parse(scope_id.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[2]); - - var var_ids = new[] - { - new { index = 0, name = "one" }, - new { index = -12, name = "bad0" }, - new { index = 1231, name = "bad1" } - }; - - var expression = $"MONO.mono_wasm_get_variables({scope}, {JsonConvert.SerializeObject(var_ids)})"; - - var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); - Assert.True(res.IsOk); - - await CheckProps(res.Value["result"]?["value"], new - { - one = TNumber(3), - bad0 = TSymbol(""), - bad1 = TSymbol("") - }, "results"); - } - [Fact] public async Task InvalidScopeId() { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs index a671ed2b7c1816..468ca4d628d376 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs @@ -641,43 +641,6 @@ await CompareObjectPropertiesFor(frame_locals, "this", }); - [Fact] - public async Task InvalidValueTypeData() - { - await CheckInspectLocalsAtBreakpointSite( - "dotnet://debugger-test.dll/debugger-test.cs", 85, 8, - "OuterMethod", - "window.setTimeout(function() { invoke_static_method ('[debugger-test] Math:OuterMethod'); })", - wait_for_event_fn: async (pause_location) => - { - var new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 4 }});"); - await _invoke_getter(new_id, "NonExistant", expect_ok: false); - - new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3 }});"); - await _invoke_getter(new_id, "NonExistant", expect_ok: false); - - new_id = await CreateNewId(@"MONO._new_or_add_id_props ({ scheme: 'valuetype', idArgs: { containerId: 1 }, props: { klass: 3, value64: 'AA' }});"); - await _invoke_getter(new_id, "NonExistant", expect_ok: false); - }); - - async Task CreateNewId(string expr) - { - var res = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), token); - Assert.True(res.IsOk, "Expected Runtime.evaluate to succeed"); - AssertEqual("string", res.Value["result"]?["type"]?.Value(), "Expected Runtime.evaluate to return a string type result"); - return res.Value["result"]?["value"]?.Value(); - } - - async Task _invoke_getter(string obj_id, string property_name, bool expect_ok) - { - var expr = $"MONO._invoke_getter ('{obj_id}', '{property_name}')"; - var res = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), token); - AssertEqual(expect_ok, res.IsOk, "Runtime.evaluate result not as expected for {expr}"); - - return res; - } - } - [Fact] public async Task MulticastDelegateTest() => await CheckInspectLocalsAtBreakpointSite( "MulticastDelegateTestClass", "Test", 5, "Test", @@ -746,7 +709,7 @@ public async Task PreviousFrameForAReflectedCall() => await CheckInspectLocalsAt await CheckProps(frame_locals, new { - mi = TObject("System.Reflection.MethodInfo"), + mi = TObject("System.Reflection.RuntimeMethodInfo"), //this is what is returned when debugging desktop apps using VS dt = TDateTime(new DateTime(4210, 3, 4, 5, 6, 7)), i = TNumber(4), strings = TArray("string[]", 1), diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 685aaaf375d85e..4a69ac3ba4f494 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -525,484 +525,52 @@ var MonoSupportLib = { }, }, - mono_wasm_get_exception_object: function() { - var exception_obj = MONO.active_exception; - MONO.active_exception = null; - return exception_obj ; - }, - - mono_wasm_get_call_stack: function() { - if (!this.mono_wasm_current_bp_id) - this.mono_wasm_current_bp_id = Module.cwrap ("mono_wasm_current_bp_id", 'number'); - if (!this.mono_wasm_enum_frames) - this.mono_wasm_enum_frames = Module.cwrap ("mono_wasm_enum_frames", null); - - var bp_id = this.mono_wasm_current_bp_id (); - this.active_frames = []; - this.mono_wasm_enum_frames (); - - var the_frames = this.active_frames; - this.active_frames = []; - return { - "breakpoint_id": bp_id, - "frames": the_frames, - }; - }, - - _fixup_name_value_objects: function (var_list) { - let out_list = []; - - var i = 0; - while (i < var_list.length) { - let o = var_list [i]; - const this_has_name = o.name !== undefined; - let next_has_value_or_get_set = false; - - if (i + 1 < var_list.length) { - const next = var_list [i+1]; - next_has_value_or_get_set = next.value !== undefined || next.get !== undefined || next.set !== undefined; - } - - if (!this_has_name) { - // insert the object as-is - // Eg. in case of locals, the names are added - // later - i ++; - } else if (next_has_value_or_get_set) { - // found a {name} followed by a {value/get} - o = Object.assign (o, var_list [i + 1]); - i += 2; - } else { - // missing value/get, so add a placeholder one - o.value = { - type: "symbol", - value: "", - description: "" - }; - i ++; - } - - out_list.push (o); - } - - return out_list; - }, - - _filter_automatic_properties: function (props, accessors_only=false) { - // Note: members in @props, have derived class members, followed by - // those from parent classes - - // Note: Auto-properties have backing fields, named with a special suffix. - // @props here will have the backing field, *and* the getter. - // - // But we want to return only one name/value pair: - // [name of the auto-property] = value of the backing field - - let getters = {}; - let all_fields_except_backing_fields = {}; - let backing_fields = {}; - - // Split props into the 3 groups - backing_fields, getters, and all_fields_except_backing_fields - props.forEach(p => { - if (p.name === undefined) { - console.debug(`Bug: Found a member with no name. Skipping it. p: ${JSON.stringify(p)}`); - return; - } - - if (p.name.endsWith('k__BackingField')) { - const auto_prop_name = p.name.replace ('k__BackingField', '') - .replace ('<', '') - .replace ('>', ''); - - // Only take the first one, as that is overriding others - if (!(auto_prop_name in backing_fields)) - backing_fields[auto_prop_name] = Object.assign(p, { name: auto_prop_name }); - - } else if (p.get !== undefined) { - // if p wasn't overridden by a getter or a field, - // from a more derived class - if (!(p.name in getters) && !(p.name in all_fields_except_backing_fields)) - getters[p.name] = p; - - } else if (!(p.name in all_fields_except_backing_fields)) { - all_fields_except_backing_fields[p.name] = p; - } - }); - - // Filter/merge backing fields, and getters - Object.values(backing_fields).forEach(backing_field => { - const auto_prop_name = backing_field.name; - const getter = getters[auto_prop_name]; - - if (getter === undefined) { - // backing field with no getter - // eg. when a field overrides/`new string foo=..` - // an autoproperty - return; - } - - if (auto_prop_name in all_fields_except_backing_fields) { - delete getters[auto_prop_name]; - } else if (getter.__args.owner_class === backing_field.__args.owner_class) { - // getter+backing_field are from the same class. - // Add the backing_field value as a field - all_fields_except_backing_fields[auto_prop_name] = backing_field; - - // .. and drop the auto-prop getter - delete getters[auto_prop_name]; + mono_wasm_add_dbg_command_received: function(res_ok, id, buffer, buffer_len) { + const assembly_data = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len); + const base64String = MONO._base64Converter.toBase64StringImpl(assembly_data); + const buffer_obj = { + res_ok, + res: { + id, + value: base64String } - }); - - if (accessors_only) - return Object.values(getters); - - return Object.values(all_fields_except_backing_fields).concat(Object.values(getters)); - }, - - /** Given `dotnet:object:foo:bar`, - * returns { scheme:'object', value: 'foo:bar' } - * - * Given `dotnet:pointer:{ b: 3 }` - * returns { scheme:'object', value: '{b:3}`, o: {b:3} - * - * @param {string} idStr - * @param {boolean} [throwOnError=false] - * - * @returns {WasmId} - */ - _parse_object_id: function (idStr, throwOnError = false) { - if (idStr === undefined || idStr == "" || !idStr.startsWith ('dotnet:')) { - if (throwOnError) - throw new Error (`Invalid id: ${idStr}`); - - return undefined; } - - const [, scheme, ...rest] = idStr.split(':'); - let res = { - scheme, - value: rest.join (':'), - idStr, - o: {} - }; - - try { - res.o = JSON.parse(res.value); - // eslint-disable-next-line no-empty - } catch (e) {} - - return res; + MONO.commands_received = buffer_obj; }, - _resolve_member_by_name: function (base_object, base_name, expr_parts) { - if (base_object === undefined || base_object.value === undefined) - throw new Error(`Bug: base_object is undefined`); - - if (base_object.value.type === 'object' && base_object.value.subtype === 'null') - throw new ReferenceError(`Null reference: ${base_name} is null`); - - if (base_object.value.type !== 'object') - throw new ReferenceError(`'.' is only supported on non-primitive types. Failed on '${base_name}'`); - - if (expr_parts.length == 0) - throw new Error(`Invalid member access expression`);//FIXME: need the full expression here - - const root = expr_parts[0]; - const props = this.mono_wasm_get_details(base_object.value.objectId, {}); - let resObject = props.find(l => l.name == root); - if (resObject !== undefined) { - if (resObject.value === undefined && resObject.get !== undefined) - resObject = this._invoke_getter(base_object.value.objectId, root); - } - - if (resObject === undefined || expr_parts.length == 1) - return resObject; - else { - expr_parts.shift(); - return this._resolve_member_by_name(resObject, root, expr_parts); - } - }, - - mono_wasm_eval_member_access: function (scope, var_list, rootObjectId, expr) { - if (expr === undefined || expr.length == 0) - throw new Error(`expression argument required`); - - let parts = expr.split('.'); - if (parts.length == 0) - throw new Error(`Invalid member access expression: ${expr}`); - - const root = parts[0]; - - const locals = this.mono_wasm_get_variables(scope, var_list); - let rootObject = locals.find(l => l.name === root); - if (rootObject === undefined) { - // check `this` - const thisObject = locals.find(l => l.name == "this"); - if (thisObject === undefined) - throw new ReferenceError(`Could not find ${root} in locals, and no 'this' found.`); - - const thisProps = this.mono_wasm_get_details(thisObject.value.objectId, {}); - rootObject = thisProps.find(tp => tp.name == root); - if (rootObject === undefined) - throw new ReferenceError(`Could not find ${root} in locals, or in 'this'`); - - if (rootObject.value === undefined && rootObject.get !== undefined) - rootObject = this._invoke_getter(thisObject.value.objectId, root); - } - - parts.shift(); - - if (parts.length == 0) - return rootObject; - - if (rootObject === undefined || rootObject.value === undefined) - 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); - var ret = this._c_fn_table.mono_wasm_set_variable_on_frame_wrapper(scope, index, name, newValue); - if (ret == false) - throw new Error(`Could not get a value for ${name}`); - return ret; - }, - - /** - * @param {WasmId} id - * @returns {object[]} - */ - _get_vt_properties: function (id, args={}) { - let entry = this._get_id_props (id.idStr); - - if (entry === undefined || entry.members === undefined) { - if (!isNaN (id.o.containerId)) { - // We are expanding, so get *all* the members. - // Which ones to return based on @args, can be determined - // at the time of return - this._get_object_properties (id.o.containerId, { expandValueTypes: true }); - } else if (!isNaN (id.o.arrayId)) - this._get_array_values (id, Number (id.o.arrayIdx), 1, true); - else - throw new Error (`Invalid valuetype id (${id.idStr}). Can't get properties for it.`); - } - - // Let's try again - entry = this._get_id_props (id.idStr); - - if (entry !== undefined && entry.members !== undefined) { - if (args.accessorPropertiesOnly === true) - return entry.accessors; - - return entry.members; - } - - throw new Error (`Unknown valuetype id: ${id.idStr}. Failed to get properties for it.`); - }, - - /** - * - * @callback GetIdArgsCallback - * @param {object} var - * @param {number} idx - * @returns {object} - */ - - /** - * @param {object[]} vars - * @param {GetIdArgsCallback} getIdArgs - * @returns {object} - */ - _assign_vt_ids: function (vars, getIdArgs) + mono_wasm_send_dbg_command_with_parms: function (id, command_set, command, command_parameters, length, valtype, newvalue) { - vars.forEach ((v, i) => { - // we might not have a `.value`, like in case of getters which have a `.get` instead - const value = v.value; - if (value === undefined || !value.isValueType) - return; - - if (value.objectId !== undefined) - throw new Error (`Bug: Trying to assign valuetype id, but the var already has one: ${v}`); - - value.objectId = this._new_or_add_id_props ({ scheme: 'valuetype', idArgs: getIdArgs (v, i), props: value._props }); - delete value._props; - }); - - return vars; - }, - - // - // @var_list: [ { index: , name: }, .. ] - mono_wasm_get_variables: function(scope, var_list) { - const numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT; - const ptr = Module._malloc(numBytes); - let heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes); - for (let i=0; i ({ containerId: this._async_method_objectId, fieldOffset: v.fieldOffset })); - - for (let i in res) { - const res_name = res [i].name; - if (this._async_method_objectId != 0) { - //Async methods are special in the way that local variables can be lifted to generated class fields - //value of "this" comes here either - if (res_name !== undefined && res_name.indexOf ('>') > 0) { - // For async methods, we get the names too, so use that - // ALTHOUGH, the name wouldn't have `<>` for method args - res [i].name = res_name.substring (1, res_name.indexOf ('>')); - } - } else if (res_name === undefined && var_list [i] !== undefined) { - // For non-async methods, we just have the var id, but we have the name - // from the caller - res [i].name = var_list [i].name; - } - } - - this._post_process_details(res); + throw new Error (`Failed on mono_wasm_invoke_method_debugger_agent_with_parms`); return res; }, - // Keep in sync with the flags in mini-wasm-debugger.c - _get_properties_args_to_gpflags: function (args) { - let gpflags =0; - /* - Disabled for now. Instead, we ask debugger.c to return - ~all~ the members, and then handle the filtering in mono.js . - - if (args.ownProperties) - gpflags |= 1; - if (args.accessorPropertiesOnly) - gpflags |= 2; - */ - if (args.expandValueTypes) - gpflags |= 4; - - return gpflags; - }, + mono_wasm_send_dbg_command: function (id, command_set, command, command_parameters) + { + const dataHeap = new Uint8Array (Module.HEAPU8.buffer, command_parameters, command_parameters.length); + dataHeap.set (new Uint8Array (this._base64_to_uint8 (command_parameters))); - /** - * @param {number} idNum - * @param {boolean} expandValueTypes - * @returns {object} - */ - _get_object_properties: function(idNum, args={}) { - let gpflags = this._get_properties_args_to_gpflags (args); + this._c_fn_table.mono_wasm_send_dbg_command_wrapper (id, command_set, command, dataHeap.byteOffset, command_parameters.length); - let { res_ok, res } = this.mono_wasm_get_object_properties_info (idNum, gpflags); + let { res_ok, res } = MONO.commands_received; if (!res_ok) - throw new Error (`Failed to get properties for ${idNum}`); - - res = MONO._filter_automatic_properties (res, args.accessorPropertiesOnly === true); - res = this._assign_vt_ids (res, v => ({ containerId: idNum, fieldOffset: v.fieldOffset })); - res = this._post_process_details (res); - + throw new Error (`Failed on mono_wasm_send_dbg_command`); return res; - }, - /** - * @param {WasmId} id - * @param {number} [startIdx=0] - * @param {number} [count=-1] - * @param {boolean} [expandValueTypes=false] - * @returns {object[]} - */ - _get_array_values: function (id, startIdx = 0, count = -1, expandValueTypes = false) { - if (isNaN (id.o.arrayId) || isNaN (startIdx)) - throw new Error (`Invalid array id: ${id.idStr}`); + }, - let gpflags = this._get_properties_args_to_gpflags({ expandValueTypes }); - let { res_ok, res } = this.mono_wasm_get_array_values_info (id.o.arrayId, startIdx, count, gpflags); + mono_wasm_get_dbg_command_info: function () + { + let { res_ok, res } = MONO.commands_received; if (!res_ok) - throw new Error (`Failed to get properties for array id ${id.idStr}`); - - res = this._assign_vt_ids (res, (_, i) => ({ arrayId: id.o.arrayId, arrayIdx: Number (startIdx) + i})); - - for (let i = 0; i < res.length; i ++) { - let value = res [i].value; - if (value.objectId !== undefined && value.objectId.startsWith("dotnet:pointer")) - this._new_or_add_id_props ({ objectId: value.objectId, props: { varName: `[${i}]` } }); - } - res = this._post_process_details (res); + throw new Error (`Failed on mono_wasm_get_dbg_command_info`); return res; }, - _post_process_details: function (details) { - if (details == undefined) - return {}; - - if (details.length > 0) - this._extract_and_cache_value_types(details); - - // remove __args added by add_properties_var - details.forEach(d => delete d.__args); - return details; - }, - - /** - * Gets the next id number to use for generating ids - * - * @returns {number} - */ - _next_id: function () { - return ++this._next_id_var; - }, - - _extract_and_cache_value_types: function (var_list) { - if (var_list == undefined || !Array.isArray (var_list) || var_list.length == 0) - return var_list; - - for (let i in var_list) { - let value = var_list [i].value; - if (value === undefined) - continue; - - if (value.objectId !== undefined && value.objectId.startsWith ("dotnet:pointer:")) { - let ptr_args = this._get_id_props (value.objectId); - if (ptr_args === undefined) - throw new Error (`Bug: Expected to find an entry for pointer id: ${value.objectId}`); - - // It might have been already set in some cases, like arrays - // where the name would be `0`, but we want `[0]` for pointers, - // so the deref would look like `*[0]` - ptr_args.varName = ptr_args.varName || var_list [i].name; - } - - if (value.type != "object" || value.isValueType != true || value.expanded != true) // undefined would also give us false - continue; - - if (value.members === undefined) { - // this could happen for valuetypes that maybe - // we were not able to describe, like `ref` parameters - // So, skip that - continue; - } - - // Generate objectId for expanded valuetypes - value.objectId = value.objectId || this._new_or_add_id_props ({ scheme: 'valuetype' }); - - this._extract_and_cache_value_types (value.members); - - const accessors = value.members.filter(m => m.get !== undefined); - const new_props = Object.assign ({ members: value.members, accessors }, value.__extra_vt_props); - - this._new_or_add_id_props ({ objectId: value.objectId, props: new_props }); - delete value.members; - delete value.__extra_vt_props; - } - - return var_list; - }, - _get_cfo_res_details: function (objectId, args) { if (!(objectId in this._call_function_res_cache)) throw new Error(`Could not find any object with id ${objectId}`); @@ -1061,116 +629,8 @@ var MonoSupportLib = { return { __value_as_json_string__: JSON.stringify (res_details) }; }, - /** - * Generates a new id, and a corresponding entry for associated properties - * like `dotnet:pointer:{ a: 4 }` - * The third segment of that `{a:4}` is the idArgs parameter - * - * Only `scheme` or `objectId` can be set. - * if `scheme`, then a new id is generated, and it's properties set - * if `objectId`, then it's properties are updated - * - * @param {object} args - * @param {string} [args.scheme=undefined] scheme second part of `dotnet:pointer:..` - * @param {string} [args.objectId=undefined] objectId - * @param {object} [args.idArgs={}] The third segment of the objectId - * @param {object} [args.props={}] Properties for the generated id - * - * @returns {string} generated/updated id string - */ - _new_or_add_id_props: function ({ scheme = undefined, objectId = undefined, idArgs = {}, props = {} }) { - if (scheme === undefined && objectId === undefined) - throw new Error (`Either scheme or objectId must be given`); - - if (scheme !== undefined && objectId !== undefined) - throw new Error (`Both scheme, and objectId cannot be given`); - - if (objectId !== undefined && Object.entries (idArgs).length > 0) - throw new Error (`Both objectId, and idArgs cannot be given`); - - if (Object.entries (idArgs).length == 0) { - // We want to generate a new id, only if it doesn't have other - // attributes that it can use to uniquely identify. - // Eg, we don't do this for `dotnet:valuetype:{containerId:4, fieldOffset: 24}` - idArgs.num = this._next_id (); - } - - let idStr; - if (objectId !== undefined) { - idStr = objectId; - const old_props = this._id_table [idStr]; - if (old_props === undefined) - throw new Error (`ObjectId not found in the id table: ${idStr}`); - - this._id_table [idStr] = Object.assign (old_props, props); - } else { - idStr = `dotnet:${scheme}:${JSON.stringify (idArgs)}`; - this._id_table [idStr] = props; - } - - return idStr; - }, - - /** - * @param {string} objectId - * @returns {object} - */ - _get_id_props: function (objectId) { - return this._id_table [objectId]; - }, - - _get_deref_ptr_value: function (objectId) { - const ptr_args = this._get_id_props (objectId); - if (ptr_args === undefined) - throw new Error (`Unknown pointer id: ${objectId}`); - - if (ptr_args.ptr_addr == 0 || ptr_args.klass_addr == 0) - throw new Error (`Both ptr_addr and klass_addr need to be non-zero, to dereference a pointer. objectId: ${objectId}`); - - const value_addr = new DataView (Module.HEAPU8.buffer).getUint32 (ptr_args.ptr_addr, /* littleEndian */ true); - let { res_ok, res } = this.mono_wasm_get_deref_ptr_value_info (value_addr, ptr_args.klass_addr); - if (!res_ok) - throw new Error (`Failed to dereference pointer ${objectId}`); - - if (res.length > 0) { - if (ptr_args.varName === undefined) - throw new Error (`Bug: no varName found for the pointer. objectId: ${objectId}`); - - res [0].name = `*${ptr_args.varName}`; - } - - res = this._post_process_details (res); - return res; - }, - mono_wasm_get_details: function (objectId, args={}) { - let id = this._parse_object_id (objectId, true); - - switch (id.scheme) { - case "object": { - if (isNaN (id.value)) - throw new Error (`Invalid objectId: ${objectId}. Expected a numeric id.`); - - args.expandValueTypes = false; - return this._get_object_properties(id.value, args); - } - - case "array": - return this._get_array_values (id); - - case "valuetype": - return this._get_vt_properties(id, args); - - case "cfo_res": - return this._get_cfo_res_details (objectId, args); - - case "pointer": { - return this._get_deref_ptr_value (objectId); - } - - default: - throw new Error(`Unknown object id format: ${objectId}`); - } + return this._get_cfo_res_details (`dotnet:cfo_res:${objectId}`, args); }, _cache_call_function_res: function (obj) { @@ -1184,105 +644,33 @@ var MonoSupportLib = { delete this._cache_call_function_res[objectId]; }, - /** - * @param {string} objectIdStr objectId - * @param {string} name property name - * @returns {object} return value - */ - _invoke_getter: function (objectIdStr, name) { - const id = this._parse_object_id (objectIdStr); - if (id === undefined) - throw new Error (`Invalid object id: ${objectIdStr}`); - - let getter_res; - if (id.scheme == 'object') { - if (isNaN (id.o) || id.o < 0) - throw new Error (`Invalid object id: ${objectIdStr}`); - - let { res_ok, res } = this.mono_wasm_invoke_getter_on_object_info (id.o, name); - if (!res_ok) - throw new Error (`Invoking getter on ${objectIdStr} failed`); - - getter_res = res; - } else if (id.scheme == 'valuetype') { - const id_props = this._get_id_props (objectIdStr); - if (id_props === undefined) - throw new Error (`Unknown valuetype id: ${objectIdStr}`); - - if (typeof id_props.value64 !== 'string' || isNaN (id_props.klass)) - throw new Error (`Bug: Cannot invoke getter on ${objectIdStr}, because of missing or invalid klass/value64 fields. idProps: ${JSON.stringify (id_props)}`); - - const dataPtr = Module._malloc (id_props.value64.length); - const dataHeap = new Uint8Array (Module.HEAPU8.buffer, dataPtr, id_props.value64.length); - dataHeap.set (new Uint8Array (this._base64_to_uint8 (id_props.value64))); - - let { res_ok, res } = this.mono_wasm_invoke_getter_on_value_info (dataHeap.byteOffset, id_props.klass, name); - Module._free (dataHeap.byteOffset); - - if (!res_ok) { - console.debug (`Invoking getter on valuetype ${objectIdStr}, with props: ${JSON.stringify (id_props)} failed`); - throw new Error (`Invoking getter on valuetype ${objectIdStr} failed`); - } - getter_res = res; - } else { - throw new Error (`Only object, and valuetypes supported for getters, id: ${objectIdStr}`); - } - - getter_res = MONO._post_process_details (getter_res); - return getter_res.length > 0 ? getter_res [0] : {}; - }, - - /** - * @param {string} objectIdStr objectId - * @param {string} name property name - * @returns {object} return true if it works and false if it doesn't - */ - _set_value_on_object: function (objectIdStr, name, newvalue) { - const id = this._parse_object_id (objectIdStr); - if (id === undefined) - throw new Error (`Invalid object id: ${objectIdStr}`); - - let setter_res; - if (id.scheme == 'object') { - if (isNaN (id.o) || id.o < 0) - throw new Error (`Invalid object id: ${objectIdStr}`); - - var ret = this._c_fn_table.mono_wasm_set_value_on_object_wrapper (id.o, name, newvalue); - if (!ret) - throw new Error (`Invoking setter on ${objectIdStr} failed`); - - setter_res = ret; - } - else - throw new Error (`Only object is supported for setters, id: ${objectIdStr}`); - return setter_res; - }, - - _create_proxy_from_object_id: function (objectId) { - const details = this.mono_wasm_get_details(objectId); - + _create_proxy_from_object_id: function (objectId, details) { if (objectId.startsWith ('dotnet:array:')) - return details.map (p => p.value); + { + let ret = details.map (p => p.value); + return ret; + } let proxy = {}; Object.keys (details).forEach (p => { var prop = details [p]; if (prop.get !== undefined) { - // TODO: `set` - Object.defineProperty (proxy, prop.name, - { get () { return MONO._invoke_getter (objectId, prop.name); } } + { get () { return MONO.mono_wasm_send_dbg_command(-1, prop.get.commandSet, prop.get.command, prop.get.buffer, prop.get.length); }, + set: function (newValue) { MONO.mono_wasm_send_dbg_command_with_parms(-1, prop.set.commandSet, prop.set.command, prop.set.buffer, prop.set.length, prop.set.valtype, newValue); return MONO.commands_received.res_ok;}} + ); + } else if (prop.set !== undefined ){ + Object.defineProperty (proxy, + prop.name, + { get () { return prop.value; }, + set: function (newValue) { MONO.mono_wasm_send_dbg_command_with_parms(-1, prop.set.commandSet, prop.set.command, prop.set.buffer, prop.set.length, prop.set.valtype, newValue); return MONO.commands_received.res_ok;}} ); } else { proxy [prop.name] = prop.value; } }); - - const handler1 = { - set (obj, prop, newValue) {return MONO._set_value_on_object (objectId, prop, newValue.toString());}, - }; - return new Proxy(proxy, handler1); + return proxy; }, mono_wasm_call_function_on: function (request) { @@ -1290,6 +678,7 @@ var MonoSupportLib = { throw new Error (`"arguments" should be an array, but was ${request.arguments}`); const objId = request.objectId; + const details = request.details; let proxy; if (objId.startsWith ('dotnet:cfo_res:')) { @@ -1298,7 +687,7 @@ var MonoSupportLib = { else throw new Error (`Unknown object id ${objId}`); } else { - proxy = this._create_proxy_from_object_id (objId); + proxy = this._create_proxy_from_object_id (objId, details); } const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : []; @@ -1308,22 +697,19 @@ var MonoSupportLib = { if (fn_res === undefined) return { type: "undefined" }; - if (fn_res === null || (fn_res.subtype === 'null' && fn_res.value === undefined)) - return fn_res; - - // primitive type if (Object (fn_res) !== fn_res) - return fn_res; - - // return .value, if it is a primitive type - if (fn_res.value !== undefined && Object (fn_res.value.value) !== fn_res.value.value) - return fn_res.value; + { + if (typeof(fn_res) == "object" && fn_res == null) + return { type: typeof(fn_res), subtype: `${fn_res}`, value: null }; + return { type: typeof(fn_res), description: `${fn_res}`, value: `${fn_res}`}; + } - if (request.returnByValue) + if (request.returnByValue && fn_res.subtype == undefined) return {type: "object", value: fn_res}; - - const fn_res_id = this._cache_call_function_res (fn_res); if (Object.getPrototypeOf (fn_res) == Array.prototype) { + + const fn_res_id = this._cache_call_function_res (fn_res); + return { type: "object", subtype: "array", @@ -1331,9 +717,15 @@ var MonoSupportLib = { description: `Array(${fn_res.length})`, objectId: fn_res_id }; - } else { - return { type: "object", className: "Object", description: "Object", objectId: fn_res_id }; } + if (fn_res.value !== undefined || fn_res.subtype !== undefined) { + return fn_res; + } + + if (fn_res == proxy) + return { type: "object", className: "Object", description: "Object", objectId: objId }; + const fn_res_id = this._cache_call_function_res (fn_res); + return { type: "object", className: "Object", description: "Object", objectId: fn_res_id }; }, _clear_per_step_state: function () { @@ -1345,31 +737,6 @@ var MonoSupportLib = { this._clear_per_step_state (); }, - mono_wasm_start_single_stepping: function (kind) { - console.debug (">> mono_wasm_start_single_stepping " + kind); - if (!this.mono_wasm_setup_single_step) - this.mono_wasm_setup_single_step = Module.cwrap ("mono_wasm_setup_single_step", 'number', [ 'number']); - - this._clear_per_step_state (); - - return this.mono_wasm_setup_single_step (kind); - }, - - mono_wasm_set_pause_on_exceptions: function (state) { - if (!this.mono_wasm_pause_on_exceptions) - this.mono_wasm_pause_on_exceptions = Module.cwrap ("mono_wasm_pause_on_exceptions", 'number', [ 'number']); - var state_enum = 0; - switch (state) { - case 'uncaught': - state_enum = 1; //EXCEPTION_MODE_UNCAUGHT - break; - case 'all': - state_enum = 2; //EXCEPTION_MODE_ALL - break; - } - return this.mono_wasm_pause_on_exceptions (state_enum); - }, - mono_wasm_detach_debugger: function () { if (!this.mono_wasm_set_is_debugger_attached) this.mono_wasm_set_is_debugger_attached = Module.cwrap ('mono_wasm_set_is_debugger_attached', 'void', ['bool']); @@ -1421,14 +788,9 @@ var MonoSupportLib = { this._call_function_res_cache = {}; this._c_fn_table = {}; - this._register_c_var_fn ('mono_wasm_get_object_properties', 'bool', [ 'number', 'number' ]); - this._register_c_var_fn ('mono_wasm_get_array_values', 'bool', [ 'number', 'number', 'number', 'number' ]); - this._register_c_var_fn ('mono_wasm_invoke_getter_on_object', 'bool', [ 'number', 'string' ]); - this._register_c_var_fn ('mono_wasm_invoke_getter_on_value', 'bool', [ 'number', 'number', 'string' ]); - this._register_c_var_fn ('mono_wasm_get_local_vars', 'bool', [ 'number', 'number', 'number']); - this._register_c_var_fn ('mono_wasm_get_deref_ptr_value', 'bool', [ 'number', 'number']); - this._register_c_fn ('mono_wasm_set_value_on_object', 'bool', [ 'number', 'string', 'string' ]); - this._register_c_fn ('mono_wasm_set_variable_on_frame', 'bool', [ 'number', 'number', 'string', 'string']); + this._register_c_fn ('mono_wasm_send_dbg_command', 'bool', [ 'number', 'number', 'number', 'number', 'number' ]); + this._register_c_fn ('mono_wasm_send_dbg_command_with_parms', 'bool', [ 'number', 'number', 'number', 'number', 'number', 'number', 'string' ]); + // DO NOT REMOVE - magic debugger init function if (globalThis.dotnetDebugger) debugger; @@ -1436,20 +798,6 @@ var MonoSupportLib = { console.debug ("mono_wasm_runtime_ready", "fe00e07a-5519-4dfe-b35a-f867dbaf2e28"); }, - mono_wasm_set_breakpoint: function (assembly, method_token, il_offset) { - if (!this.mono_wasm_set_bp) - this.mono_wasm_set_bp = Module.cwrap ('mono_wasm_set_breakpoint', 'number', ['string', 'number', 'number']); - - return this.mono_wasm_set_bp (assembly, method_token, il_offset) - }, - - mono_wasm_remove_breakpoint: function (breakpoint_id) { - if (!this.mono_wasm_del_bp) - this.mono_wasm_del_bp = Module.cwrap ('mono_wasm_remove_breakpoint', 'number', ['number']); - - return this.mono_wasm_del_bp (breakpoint_id); - }, - // Set environment variable NAME to VALUE // Should be called before mono_load_runtime_and_bcl () in most cases mono_wasm_setenv: function (name, value) { @@ -1960,78 +1308,6 @@ var MonoSupportLib = { return MONO.loaded_assets; }, - mono_wasm_clear_all_breakpoints: function() { - if (!this.mono_clear_bps) - this.mono_clear_bps = Module.cwrap ('mono_wasm_clear_all_breakpoints', null); - - this.mono_clear_bps (); - }, - - mono_wasm_add_null_var: function(className) - { - let fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); - if (!fixed_class_name) { - // Eg, when a @className is passed from js itself, like - // mono_wasm_add_null_var ("string") - fixed_class_name = className; - } - MONO.var_info.push ({value: { - type: "object", - className: fixed_class_name, - description: fixed_class_name, - subtype: "null" - }}); - }, - - _mono_wasm_add_string_var: function(var_value) { - if (var_value === 0) { - MONO.mono_wasm_add_null_var ("string"); - return; - } - - MONO.var_info.push({ - value: { - type: "string", - value: var_value, - description: var_value - } - }); - }, - - _mono_wasm_add_getter_var: function(className) { - const fixed_class_name = MONO._mono_csharp_fixup_class_name (className); - var name; - if (MONO.var_info.length > 0) - name = MONO.var_info [MONO.var_info.length - 1].name; - name = (name === undefined) ? "" : name; - - MONO.var_info.push({ - get: { - className: "Function", - description: `get ${name} () {}`, - type: "function", - } - }); - }, - - _mono_wasm_add_array_var: function(className, objectId, length) { - const fixed_class_name = MONO._mono_csharp_fixup_class_name(className); - if (objectId == 0) { - MONO.mono_wasm_add_null_var (fixed_class_name); - return; - } - - MONO.var_info.push({ - value: { - type: "object", - subtype: "array", - className: fixed_class_name, - description: `${fixed_class_name}(${length})`, - objectId: this._new_or_add_id_props ({ scheme: 'array', idArgs: { arrayId: objectId } }) - } - }); - }, - // FIXME: improve _base64_to_uint8: function (base64String) { const byteCharacters = atob (base64String); @@ -2043,215 +1319,6 @@ var MonoSupportLib = { return new Uint8Array (byteNumbers); }, - _begin_value_type_var: function(className, args) { - if (args === undefined || (typeof args !== 'object')) { - console.debug (`_begin_value_type_var: Expected an args object`); - return; - } - - const fixed_class_name = MONO._mono_csharp_fixup_class_name(className); - const toString = args.toString; - const base64String = btoa (String.fromCharCode (...new Uint8Array (Module.HEAPU8.buffer, args.value_addr, args.value_size))); - const vt_obj = { - value: { - type : "object", - className : fixed_class_name, - description : (toString === 0 ? fixed_class_name: Module.UTF8ToString (toString)), - expanded : true, - isValueType : true, - __extra_vt_props: { klass: args.klass, value64: base64String }, - members : [] - } - }; - if (MONO._vt_stack.length == 0) - MONO._old_var_info = MONO.var_info; - - MONO.var_info = vt_obj.value.members; - MONO._vt_stack.push (vt_obj); - }, - - _end_value_type_var: function() { - let top_vt_obj_popped = MONO._vt_stack.pop (); - top_vt_obj_popped.value.members = MONO._filter_automatic_properties ( - MONO._fixup_name_value_objects (top_vt_obj_popped.value.members)); - - if (MONO._vt_stack.length == 0) { - MONO.var_info = MONO._old_var_info; - MONO.var_info.push(top_vt_obj_popped); - } else { - var top_obj = MONO._vt_stack [MONO._vt_stack.length - 1]; - top_obj.value.members.push (top_vt_obj_popped); - MONO.var_info = top_obj.value.members; - } - }, - - _add_valuetype_unexpanded_var: function(className, args) { - if (args === undefined || (typeof args !== 'object')) { - console.debug (`_add_valuetype_unexpanded_var: Expected an args object`); - return; - } - - const fixed_class_name = MONO._mono_csharp_fixup_class_name (className); - const toString = args.toString; - - MONO.var_info.push ({ - value: { - type: "object", - className: fixed_class_name, - description: (toString === 0 ? fixed_class_name : Module.UTF8ToString (toString)), - isValueType: true - } - }); - }, - - mono_wasm_add_properties_var: function (name, args) { - if (typeof args !== 'object') - args = { field_offset: args }; - - if (args.owner_class !== undefined && args.owner_class !== 0) - args.owner_class = Module.UTF8ToString(args.owner_class); - - let name_obj = { - name: Module.UTF8ToString (name), - fieldOffset: args.field_offset, - __args: args - }; - if (args.is_own) - name_obj.isOwn = true; - - MONO.var_info.push(name_obj); - }, - - mono_wasm_add_typed_value: function (type, str_value, value) { - let type_str = type; - if (typeof type != 'string') - type_str = Module.UTF8ToString (type); - - if (str_value !== 0) - str_value = Module.UTF8ToString (str_value); - - switch (type_str) { - case "bool": { - const v = value != 0; - MONO.var_info.push ({ - value: { - type: "boolean", - value: v, - description: v.toString () - }, - writable:true - }); - break; - } - - case "char": { - const v = `${value} '${String.fromCharCode (value)}'`; - MONO.var_info.push ({ - value: { - type: "symbol", - value: v, - description: v - }, - writable:true - }); - break; - } - - case "number": - MONO.var_info.push ({ - value: { - type: "number", - value: value, - description: '' + value - }, - writable:true - }); - break; - - case "string": - MONO._mono_wasm_add_string_var (str_value); - break; - - case "getter": - MONO._mono_wasm_add_getter_var (str_value); - break; - - case "array": - MONO._mono_wasm_add_array_var (str_value, value.objectId, value.length); - break; - - case "begin_vt": - MONO._begin_value_type_var (str_value, value); - break; - - case "end_vt": - MONO._end_value_type_var (); - break; - - case "unexpanded_vt": - MONO._add_valuetype_unexpanded_var (str_value, value); - break; - - case "pointer": { - const fixed_value_str = MONO._mono_csharp_fixup_class_name (str_value); - if (value.klass_addr == 0 || value.ptr_addr == 0 || fixed_value_str.startsWith ('(void*')) { - // null or void*, which we can't deref - MONO.var_info.push({ - value: { - type: "symbol", - value: fixed_value_str, - description: fixed_value_str - } - }); - } else { - MONO.var_info.push({ - value: { - type: "object", - className: fixed_value_str, - description: fixed_value_str, - objectId: this._new_or_add_id_props ({ scheme: 'pointer', props: value }) - } - }); - } - } - break; - - case "symbol": { - if (typeof value === 'object' && value.isClassName) - str_value = MONO._mono_csharp_fixup_class_name (str_value); - - MONO.var_info.push ({ - value: { - type: "symbol", - value: str_value, - description: str_value - } - }); - } - break; - - default: { - const msg = `'${str_value}' ${value}`; - - MONO.var_info.push ({ - value: { - type: "symbol", - value: msg, - description: msg - } - }); - break; - } - } - }, - - _mono_csharp_fixup_class_name: function(className) - { - // Fix up generic names like Foo`2 to Foo - // and nested class names like Foo/Bar to Foo.Bar - return className.replace(/\//g, '.').replace(/`\d+/g, ''); - }, - mono_wasm_load_data_archive: function (data, prefix) { if (data.length < 8) return false; @@ -2324,132 +1391,6 @@ var MonoSupportLib = { console.debug('mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae', JSON.stringify(event), JSON.stringify(args)); }, }, - - mono_wasm_add_typed_value: function (type, str_value, value) { - MONO.mono_wasm_add_typed_value (type, str_value, value); - }, - - mono_wasm_add_properties_var: function(name, args) { - MONO.mono_wasm_add_properties_var (name, args); - }, - - mono_wasm_set_is_async_method: function(objectId) { - MONO._async_method_objectId = objectId; - }, - - mono_wasm_add_enum_var: function(className, members, value) { - // FIXME: flags - // - - // group0: Monday:0 - // group1: Monday - // group2: 0 - const re = new RegExp (`[,]?([^,:]+):(${value}(?=,)|${value}$)`, 'g') - const members_str = Module.UTF8ToString (members); - - const match = re.exec(members_str); - const member_name = match == null ? ('' + value) : match [1]; - - const fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); - MONO.var_info.push({ - value: { - type: "object", - className: fixed_class_name, - description: member_name, - isEnum: true - } - }); - }, - - mono_wasm_add_array_item: function(position) { - MONO.var_info.push({ - name: `${position}` - }); - }, - - mono_wasm_add_obj_var: function(className, toString, objectId) { - if (objectId == 0) { - MONO.mono_wasm_add_null_var (className); - return; - } - - const fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); - MONO.var_info.push({ - value: { - type: "object", - className: fixed_class_name, - description: (toString === 0 ? fixed_class_name : Module.UTF8ToString (toString)), - objectId: "dotnet:object:"+ objectId, - } - }); - }, - - /* - * @className, and @targetName are in the following format: - * - * :[]: - */ - mono_wasm_add_func_var: function (className, targetName, objectId) { - if (objectId == 0) { - MONO.mono_wasm_add_null_var ( - MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className))); - return; - } - - function args_to_sig (args_str) { - var parts = args_str.split (":"); - // TODO: min length = 3? - parts = parts.map (a => MONO._mono_csharp_fixup_class_name (a)); - - // method name at the end - var method_name = parts.pop (); - - // ret type at the beginning - var ret_sig = parts [0]; - var args_sig = parts.splice (1).join (', '); - return `${ret_sig} ${method_name} (${args_sig})`; - } - let tgt_sig; - if (targetName != 0) - tgt_sig = args_to_sig (Module.UTF8ToString (targetName)); - - const type_name = MONO._mono_csharp_fixup_class_name (Module.UTF8ToString (className)); - if (tgt_sig === undefined) - tgt_sig = type_name; - - if (objectId == -1 || targetName === 0) { - // Target property - MONO.var_info.push ({ - value: { - type: "symbol", - value: tgt_sig, - description: tgt_sig, - } - }); - } else { - MONO.var_info.push ({ - value: { - type: "object", - className: type_name, - description: tgt_sig, - objectId: "dotnet:object:" + objectId, - } - }); - } - }, - - mono_wasm_add_frame: function(il, method, frame_id, assembly_name, method_full_name) { - var parts = Module.UTF8ToString (method_full_name).split (":", 2); - MONO.active_frames.push( { - il_pos: il, - method_token: method, - assembly_name: Module.UTF8ToString (assembly_name), - // Extract just the method name from `{class_name}:{method_name}` - method_name: parts [parts.length - 1], - frame_id - }); - }, - schedule_background_exec: function () { ++MONO.pump_count; if (typeof globalThis.setTimeout === 'function') { @@ -2473,21 +1414,11 @@ var MonoSupportLib = { } }, - mono_wasm_fire_bp: function () { + mono_wasm_fire_debugger_agent_message: function () { // eslint-disable-next-line no-debugger debugger; }, - mono_wasm_fire_exception: function (exception_id, message, class_name, uncaught) { - MONO.active_exception = { - exception_id: exception_id, - message : Module.UTF8ToString (message), - class_name : Module.UTF8ToString (class_name), - uncaught : uncaught - }; - debugger; - }, - mono_wasm_asm_loaded: function (assembly_name, assembly_ptr, assembly_len, pdb_ptr, pdb_len) { // Only trigger this codepath for assemblies loaded after app is ready if (MONO.mono_wasm_runtime_is_ready !== true)