[wasm] [debugger] Adding test for dotnet/runtime#42227 PR (#20411)
[mono-project.git] / mono / mini / mini-wasm-debugger.c
blob76eeede1d999a9c9f994914b00b15c958006a037
1 #include "mini.h"
2 #include "mini-runtime.h"
3 #include <mono/metadata/mono-debug.h>
4 #include <mono/metadata/assembly.h>
5 #include <mono/metadata/assembly-internals.h>
6 #include <mono/metadata/metadata.h>
7 #include <mono/metadata/metadata-internals.h>
8 #include <mono/metadata/mono-endian.h>
9 #include <mono/metadata/seq-points-data.h>
10 #include <mono/mini/aot-runtime.h>
11 #include <mono/mini/seq-points.h>
12 #include <mono/mini/debugger-engine.h>
14 //XXX This is dirty, extend ee.h to support extracting info from MonoInterpFrameHandle
15 #include <mono/mini/interp/interp-internals.h>
17 #ifdef HOST_WASM
19 #include <emscripten.h>
21 #include "mono/metadata/assembly-internals.h"
22 #include "mono/metadata/debug-mono-ppdb.h"
24 static int log_level = 1;
26 #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { fprintf (stdout, __VA_ARGS__); } } while (0)
28 enum {
29 EXCEPTION_MODE_NONE,
30 EXCEPTION_MODE_UNCAUGHT,
31 EXCEPTION_MODE_ALL
34 // Flags for get_*_properties
35 #define GPFLAG_NONE 0x0000
36 #define GPFLAG_OWN_PROPERTIES 0x0001
37 #define GPFLAG_ACCESSORS_ONLY 0x0002
38 #define GPFLAG_EXPAND_VALUETYPES 0x0004
40 //functions exported to be used by JS
41 G_BEGIN_DECLS
43 EMSCRIPTEN_KEEPALIVE int mono_wasm_set_breakpoint (const char *assembly_name, int method_token, int il_offset);
44 EMSCRIPTEN_KEEPALIVE int mono_wasm_remove_breakpoint (int bp_id);
45 EMSCRIPTEN_KEEPALIVE int mono_wasm_current_bp_id (void);
46 EMSCRIPTEN_KEEPALIVE void mono_wasm_enum_frames (void);
47 EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_local_vars (int scope, int* pos, int len);
48 EMSCRIPTEN_KEEPALIVE void mono_wasm_clear_all_breakpoints (void);
49 EMSCRIPTEN_KEEPALIVE int mono_wasm_setup_single_step (int kind);
50 EMSCRIPTEN_KEEPALIVE int mono_wasm_pause_on_exceptions (int state);
51 EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_object_properties (int object_id, int gpflags);
52 EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_array_values (int object_id, int start_idx, int count, int gpflags);
53 EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_object (int object_id, const char* name);
54 EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name);
55 EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass);
57 //JS functions imported that we use
58 extern void mono_wasm_add_frame (int il_offset, int method_token, int frame_id, const char *assembly_name, const char *method_name);
59 extern void mono_wasm_fire_bp (void);
60 extern void mono_wasm_fire_exception (int exception_obj_id, const char* message, const char* class_name, gboolean uncaught);
61 extern void mono_wasm_add_obj_var (const char*, const char*, guint64);
62 extern void mono_wasm_add_enum_var (const char*, const char*, guint64);
63 extern void mono_wasm_add_func_var (const char*, const char*, guint64);
64 extern void mono_wasm_add_properties_var (const char*, gint32);
65 extern void mono_wasm_add_array_item (int);
66 extern void mono_wasm_set_is_async_method (guint64);
67 extern void mono_wasm_add_typed_value (const char *type, const char *str_value, double value);
68 extern void mono_wasm_asm_loaded (const char *asm_name, const char *assembly_data, guint32 assembly_len, const char *pdb_data, guint32 pdb_len);
70 G_END_DECLS
72 static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags);
73 static void handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame);
74 static void assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly);
76 //FIXME move all of those fields to the profiler object
77 static gboolean debugger_enabled;
79 static int event_request_id;
80 static GHashTable *objrefs;
81 static GHashTable *obj_to_objref;
82 static int objref_id = 0;
83 static int pause_on_exc = EXCEPTION_MODE_NONE;
85 static const char*
86 all_getters_allowed_class_names[] = {
87 "System.DateTime",
88 "System.DateTimeOffset",
89 "System.TimeSpan"
92 static const char*
93 to_string_as_descr_names[] = {
94 "System.DateTime",
95 "System.DateTimeOffset",
96 "System.Decimal",
97 "System.TimeSpan"
100 #define THREAD_TO_INTERNAL(thread) (thread)->internal_thread
102 static void
103 inplace_tolower (char *c)
105 int i;
106 for (i = strlen (c) - 1; i >= 0; --i)
107 c [i] = tolower (c [i]);
110 static void
111 jit_done (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo)
113 mono_de_add_pending_breakpoints (method, jinfo);
116 static void
117 appdomain_load (MonoProfiler *prof, MonoDomain *domain)
119 mono_de_domain_add (domain);
122 /* Frame state handling */
123 static GPtrArray *frames;
125 static void
126 free_frame (DbgEngineStackFrame *frame)
128 g_free (frame);
131 static gboolean
132 collect_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data)
134 SeqPoint sp;
135 MonoMethod *method;
137 //skip wrappers
138 if (info->type != FRAME_TYPE_MANAGED && info->type != FRAME_TYPE_INTERP)
139 return FALSE;
141 if (info->ji)
142 method = jinfo_get_method (info->ji);
143 else
144 method = info->method;
146 if (!method)
147 return FALSE;
149 DEBUG_PRINTF (2, "collect_frames: Reporting method %s native_offset %d, wrapper_type: %d\n", method->name, info->native_offset, method->wrapper_type);
151 if (!mono_find_prev_seq_point_for_native_offset (mono_get_root_domain (), method, info->native_offset, NULL, &sp))
152 DEBUG_PRINTF (2, "collect_frames: Failed to lookup sequence point. method: %s, native_offset: %d \n", method->name, info->native_offset);
155 StackFrame *frame = g_new0 (StackFrame, 1);
156 frame->de.ji = info->ji;
157 frame->de.domain = info->domain;
158 frame->de.method = method;
159 frame->de.native_offset = info->native_offset;
161 frame->il_offset = info->il_offset;
162 frame->interp_frame = info->interp_frame;
163 frame->frame_addr = info->frame_addr;
165 g_ptr_array_add (frames, frame);
167 return FALSE;
170 static void
171 free_frame_state (void)
173 if (frames) {
174 int i;
175 for (i = 0; i < frames->len; ++i)
176 free_frame ((DbgEngineStackFrame*)g_ptr_array_index (frames, i));
177 g_ptr_array_set_size (frames, 0);
181 static void
182 compute_frames (void) {
183 if (frames) {
184 int i;
185 for (i = 0; i < frames->len; ++i)
186 free_frame ((DbgEngineStackFrame*)g_ptr_array_index (frames, i));
187 g_ptr_array_set_size (frames, 0);
188 } else {
189 frames = g_ptr_array_new ();
192 mono_walk_stack_with_ctx (collect_frames, NULL, MONO_UNWIND_NONE, NULL);
194 static MonoContext*
195 tls_get_restore_state (void *tls)
197 return NULL;
200 static gboolean
201 try_process_suspend (void *tls, MonoContext *ctx, gboolean from_breakpoint)
203 return FALSE;
206 static gboolean
207 begin_breakpoint_processing (void *tls, MonoContext *ctx, MonoJitInfo *ji, gboolean from_signal)
209 return TRUE;
212 static void
213 begin_single_step_processing (MonoContext *ctx, gboolean from_signal)
217 static void
218 ss_discard_frame_context (void *the_tls)
220 free_frame_state ();
223 static void
224 ss_calculate_framecount (void *tls, MonoContext *ctx, gboolean force_use_ctx, DbgEngineStackFrame ***out_frames, int *nframes)
226 compute_frames ();
227 if (out_frames)
228 *out_frames = (DbgEngineStackFrame **)frames->pdata;
229 if (nframes)
230 *nframes = frames->len;
233 static gboolean
234 ensure_jit (DbgEngineStackFrame* the_frame)
236 return TRUE;
239 static int
240 ensure_runtime_is_suspended (void)
242 return DE_ERR_NONE;
245 static int
246 get_object_id (MonoObject *obj)
248 ObjRef *ref;
249 if (!obj)
250 return 0;
252 ref = (ObjRef *)g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)));
253 if (ref)
254 return ref->id;
255 ref = g_new0 (ObjRef, 1);
256 ref->id = mono_atomic_inc_i32 (&objref_id);
257 ref->handle = mono_gchandle_new_weakref_internal (obj, FALSE);
258 g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref);
259 g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref);
260 return ref->id;
264 static int
265 get_this_async_id (DbgEngineStackFrame *frame)
267 MonoClassField *builder_field;
268 gpointer builder;
269 MonoMethod *method;
270 MonoObject *ex;
271 ERROR_DECL (error);
272 MonoObject *obj;
275 * FRAME points to a method in a state machine class/struct.
276 * Call the ObjectIdForDebugger method of the associated method builder type.
278 builder = get_async_method_builder (frame);
279 if (!builder)
280 return 0;
282 builder_field = mono_class_get_field_from_name_full (get_class_to_get_builder_field(frame), "<>t__builder", NULL);
283 if (!builder_field)
284 return 0;
286 method = get_object_id_for_debugger_method (mono_class_from_mono_type_internal (builder_field->type));
287 if (!method) {
288 return 0;
291 obj = mono_runtime_try_invoke (method, builder, NULL, &ex, error);
292 mono_error_assert_ok (error);
294 return get_object_id (obj);
297 typedef struct {
298 gboolean is_ss; //do I need this?
299 } BpEvents;
301 static void*
302 create_breakpoint_events (GPtrArray *ss_reqs, GPtrArray *bp_reqs, MonoJitInfo *ji, EventKind kind)
304 DEBUG_PRINTF (1, "ss_reqs %d bp_reqs %d\n", ss_reqs->len, bp_reqs->len);
305 if ((ss_reqs && ss_reqs->len) || (bp_reqs && bp_reqs->len)) {
306 BpEvents *evts = g_new0 (BpEvents, 1); //just a non-null value to make sure we can raise it on process_breakpoint_events
307 evts->is_ss = (ss_reqs && ss_reqs->len);
308 return evts;
310 return NULL;
313 static void
314 process_breakpoint_events (void *_evts, MonoMethod *method, MonoContext *ctx, int il_offsets)
316 BpEvents *evts = (BpEvents*)_evts;
317 if (evts) {
318 if (evts->is_ss)
319 mono_de_cancel_all_ss ();
320 mono_wasm_fire_bp ();
321 g_free (evts);
325 static void
326 no_seq_points_found (MonoMethod *method, int offset)
329 * This can happen in full-aot mode with assemblies AOTed without the 'soft-debug' option to save space.
331 DEBUG_PRINTF (1, "Unable to find seq points for method '%s', offset 0x%x.\n", mono_method_full_name (method, TRUE), offset);
334 #define DBG_NOT_SUSPENDED 1
336 static int
337 ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *ss_args)
339 DEBUG_PRINTF (1, "ss_create_init_args\n");
340 int dummy = 0;
341 ss_req->start_sp = ss_req->last_sp = &dummy;
342 compute_frames ();
343 memset (ss_args, 0, sizeof (*ss_args));
345 // This shouldn't happen - maybe should assert here ?
346 if (frames->len == 0) {
347 DEBUG_PRINTF (1, "SINGLE STEPPING FOUND NO FRAMES");
348 return DBG_NOT_SUSPENDED;
351 DbgEngineStackFrame *frame = (DbgEngineStackFrame*)g_ptr_array_index (frames, 0);
352 ss_req->start_method = ss_args->method = frame->method;
353 gboolean found_sp = mono_find_prev_seq_point_for_native_offset (frame->domain, frame->method, frame->native_offset, &ss_args->info, &ss_args->sp);
354 if (!found_sp)
355 no_seq_points_found (frame->method, frame->native_offset);
356 g_assert (found_sp);
358 ss_args->frames = (DbgEngineStackFrame**)frames->pdata;
359 ss_args->nframes = frames->len;
360 //XXX do sp
362 return DE_ERR_NONE;
365 static void
366 ss_args_destroy (SingleStepArgs *ss_args)
368 //nothing to do
371 static int
372 handle_multiple_ss_requests (void) {
373 mono_de_cancel_all_ss ();
374 return 1;
377 void
378 mono_wasm_debugger_init (void)
380 if (!debugger_enabled)
381 return;
383 DebuggerEngineCallbacks cbs = {
384 .tls_get_restore_state = tls_get_restore_state,
385 .try_process_suspend = try_process_suspend,
386 .begin_breakpoint_processing = begin_breakpoint_processing,
387 .begin_single_step_processing = begin_single_step_processing,
388 .ss_discard_frame_context = ss_discard_frame_context,
389 .ss_calculate_framecount = ss_calculate_framecount,
390 .ensure_jit = ensure_jit,
391 .ensure_runtime_is_suspended = ensure_runtime_is_suspended,
392 .get_this_async_id = get_this_async_id,
393 .set_set_notification_for_wait_completion_flag = set_set_notification_for_wait_completion_flag,
394 .get_notify_debugger_of_wait_completion_method = get_notify_debugger_of_wait_completion_method,
395 .create_breakpoint_events = create_breakpoint_events,
396 .process_breakpoint_events = process_breakpoint_events,
397 .ss_create_init_args = ss_create_init_args,
398 .ss_args_destroy = ss_args_destroy,
399 .handle_multiple_ss_requests = handle_multiple_ss_requests,
402 mono_debug_init (MONO_DEBUG_FORMAT_MONO);
403 mono_de_init (&cbs);
404 mono_de_set_log_level (log_level, stdout);
406 mini_debug_options.gen_sdb_seq_points = TRUE;
407 mini_debug_options.mdb_optimizations = TRUE;
408 mono_disable_optimizations (MONO_OPT_LINEARS);
409 mini_debug_options.load_aot_jit_info_eagerly = TRUE;
411 MonoProfilerHandle prof = mono_profiler_create (NULL);
412 mono_profiler_set_jit_done_callback (prof, jit_done);
413 //FIXME support multiple appdomains
414 mono_profiler_set_domain_loaded_callback (prof, appdomain_load);
415 mono_profiler_set_assembly_loaded_callback (prof, assembly_loaded);
417 obj_to_objref = g_hash_table_new (NULL, NULL);
418 objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref);
420 mini_get_dbg_callbacks ()->handle_exception = handle_exception;
423 MONO_API void
424 mono_wasm_enable_debugging (int debug_level)
426 DEBUG_PRINTF (1, "DEBUGGING ENABLED\n");
427 debugger_enabled = TRUE;
428 log_level = debug_level;
431 EMSCRIPTEN_KEEPALIVE int
432 mono_wasm_pause_on_exceptions (int state)
434 pause_on_exc = state;
435 DEBUG_PRINTF (1, "setting pause on exception: %d\n", pause_on_exc);
436 return 1;
439 EMSCRIPTEN_KEEPALIVE int
440 mono_wasm_setup_single_step (int kind)
442 int nmodifiers = 1;
444 DEBUG_PRINTF (2, ">>>> mono_wasm_setup_single_step %d\n", kind);
445 EventRequest *req = (EventRequest *)g_malloc0 (sizeof (EventRequest) + (nmodifiers * sizeof (Modifier)));
446 req->id = ++event_request_id;
447 req->event_kind = EVENT_KIND_STEP;
448 // DE doesn't care about suspend_policy
449 // req->suspend_policy = SUSPEND_POLICY_ALL;
450 req->nmodifiers = nmodifiers;
452 StepSize size = STEP_SIZE_MIN;
454 //FIXME I DON'T KNOW WHAT I'M DOING!!!!! filter all the things.
455 StepFilter filter = (StepFilter)(STEP_FILTER_STATIC_CTOR | STEP_FILTER_DEBUGGER_HIDDEN | STEP_FILTER_DEBUGGER_STEP_THROUGH | STEP_FILTER_DEBUGGER_NON_USER_CODE);
456 req->modifiers [0].data.filter = filter;
458 StepDepth depth;
459 switch (kind) {
460 case 0: //into
461 depth = STEP_DEPTH_INTO;
462 break;
463 case 1: //out
464 depth = STEP_DEPTH_OUT;
465 break;
466 case 2: //over
467 depth = STEP_DEPTH_OVER;
468 break;
469 default:
470 g_error ("[dbg] unknown step kind %d", kind);
473 DbgEngineErrorCode err = mono_de_ss_create (THREAD_TO_INTERNAL (mono_thread_current ()), size, depth, filter, req);
474 if (err != DE_ERR_NONE) {
475 DEBUG_PRINTF (1, "[dbg] Failed to setup single step request");
477 DEBUG_PRINTF (1, "[dbg] single step is in place, now what?\n");
478 SingleStepReq *ss_req = req->info;
479 int isBPOnNativeCode = 0;
480 if (ss_req && ss_req->bps) {
481 GSList *l;
483 for (l = ss_req->bps; l; l = l->next) {
484 if (((MonoBreakpoint *)l->data)->method->wrapper_type != MONO_WRAPPER_RUNTIME_INVOKE)
485 isBPOnNativeCode = 1;
488 if (!isBPOnNativeCode) {
489 mono_de_cancel_all_ss ();
491 return isBPOnNativeCode;
494 static void
495 assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly)
497 DEBUG_PRINTF (2, "assembly_loaded callback called for %s\n", assembly->aname.name);
498 MonoImage *assembly_image = assembly->image;
499 MonoImage *pdb_image = NULL;
500 if (mono_has_pdb_checksum ((char *) assembly_image->raw_data, assembly_image->raw_data_len)) { //if it's a release assembly we don't need to send to DebuggerProxy
501 MonoDebugHandle *handle = mono_debug_get_handle (assembly_image);
502 if (handle) {
503 MonoPPDBFile *ppdb = handle->ppdb;
504 if (!mono_ppdb_is_embedded (ppdb)) { //if it's an embedded pdb we don't need to send pdb extrated to DebuggerProxy.
505 pdb_image = mono_ppdb_get_image (ppdb);
506 mono_wasm_asm_loaded (assembly_image->assembly_name, assembly_image->raw_data, assembly_image->raw_data_len, pdb_image->raw_data, pdb_image->raw_data_len);
507 return;
510 mono_wasm_asm_loaded (assembly_image->assembly_name, assembly_image->raw_data, assembly_image->raw_data_len, NULL, 0);
514 static void
515 handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame)
517 ERROR_DECL (error);
518 DEBUG_PRINTF (1, "handle exception - %d - %p - %p - %p\n", pause_on_exc, exc, throw_ctx, catch_ctx);
520 if (pause_on_exc == EXCEPTION_MODE_NONE)
521 return;
522 if (pause_on_exc == EXCEPTION_MODE_UNCAUGHT && catch_ctx != NULL)
523 return;
525 int obj_id = get_object_id ((MonoObject *)exc);
526 const char *error_message = mono_string_to_utf8_checked_internal (exc->message, error);
528 if (!is_ok (error))
529 error_message = "Failed to get exception message.";
531 const char *class_name = mono_class_full_name (mono_object_class (exc));
532 DEBUG_PRINTF (2, "handle exception - calling mono_wasm_fire_exc(): %d - message - %s, class_name: %s\n", obj_id, error_message, class_name);
534 mono_wasm_fire_exception (obj_id, error_message, class_name, !catch_ctx);
536 DEBUG_PRINTF (2, "handle exception - done\n");
540 EMSCRIPTEN_KEEPALIVE void
541 mono_wasm_clear_all_breakpoints (void)
543 DEBUG_PRINTF (1, "CLEAR BREAKPOINTS\n");
544 mono_de_clear_all_breakpoints ();
547 EMSCRIPTEN_KEEPALIVE int
548 mono_wasm_set_breakpoint (const char *assembly_name, int method_token, int il_offset)
550 int i;
551 ERROR_DECL (error);
552 DEBUG_PRINTF (1, "SET BREAKPOINT: assembly %s method %x offset %x\n", assembly_name, method_token, il_offset);
555 //we get 'foo.dll' but mono_assembly_load expects 'foo' so we strip the last dot
556 char *lookup_name = g_strdup (assembly_name);
557 for (i = strlen (lookup_name) - 1; i >= 0; --i) {
558 if (lookup_name [i] == '.') {
559 lookup_name [i] = 0;
560 break;
564 //resolve the assembly
565 MonoImageOpenStatus status;
566 MonoAssemblyName* aname = mono_assembly_name_new (lookup_name);
567 MonoAssemblyByNameRequest byname_req;
568 mono_assembly_request_prepare_byname (&byname_req, MONO_ASMCTX_DEFAULT, mono_domain_default_alc (mono_get_root_domain ()));
569 MonoAssembly *assembly = mono_assembly_request_byname (aname, &byname_req, &status);
570 g_free (lookup_name);
571 if (!assembly) {
572 DEBUG_PRINTF (1, "Could not resolve assembly %s\n", assembly_name);
573 return -1;
576 mono_assembly_name_free_internal (aname);
578 MonoMethod *method = mono_get_method_checked (assembly->image, MONO_TOKEN_METHOD_DEF | method_token, NULL, NULL, error);
579 if (!method) {
580 //FIXME don't swallow the error
581 DEBUG_PRINTF (1, "Could not find method due to %s\n", mono_error_get_message (error));
582 mono_error_cleanup (error);
583 return -1;
586 //FIXME right now none of the EventRequest fields are used by debugger-engine
587 EventRequest *req = g_new0 (EventRequest, 1);
588 req->id = ++event_request_id;
589 req->event_kind = EVENT_KIND_BREAKPOINT;
590 //DE doesn't care about suspend_policy
591 // req->suspend_policy = SUSPEND_POLICY_ALL;
592 req->nmodifiers = 0; //funny thing,
594 // BreakPointRequest *req = breakpoint_request_new (assembly, method, il_offset);
595 MonoBreakpoint *bp = mono_de_set_breakpoint (method, il_offset, req, error);
597 if (!bp) {
598 DEBUG_PRINTF (1, "Could not set breakpoint to %s\n", mono_error_get_message (error));
599 mono_error_cleanup (error);
600 return 0;
603 DEBUG_PRINTF (1, "NEW BP %p has id %d\n", req, req->id);
604 return req->id;
607 EMSCRIPTEN_KEEPALIVE int
608 mono_wasm_remove_breakpoint (int bp_id)
610 MonoBreakpoint *bp = mono_de_get_breakpoint_by_id (bp_id);
611 if (!bp)
612 return 0;
614 mono_de_clear_breakpoint (bp);
615 return 1;
618 void
619 mono_wasm_single_step_hit (void)
621 mono_de_process_single_step (NULL, FALSE);
624 void
625 mono_wasm_breakpoint_hit (void)
627 mono_de_process_breakpoint (NULL, FALSE);
628 // mono_wasm_fire_bp ();
631 EMSCRIPTEN_KEEPALIVE int
632 mono_wasm_current_bp_id (void)
634 DEBUG_PRINTF (2, "COMPUTING breakpoint ID\n");
635 //FIXME handle compiled case
637 /* Interpreter */
638 MonoLMF *lmf = mono_get_lmf ();
640 g_assert (((guint64)lmf->previous_lmf) & 2);
641 MonoLMFExt *ext = (MonoLMFExt*)lmf;
643 g_assert (ext->kind == MONO_LMFEXT_INTERP_EXIT || ext->kind == MONO_LMFEXT_INTERP_EXIT_WITH_CTX);
644 MonoInterpFrameHandle *frame = (MonoInterpFrameHandle*)ext->interp_exit_data;
645 MonoJitInfo *ji = mini_get_interp_callbacks ()->frame_get_jit_info (frame);
646 guint8 *ip = (guint8*)mini_get_interp_callbacks ()->frame_get_ip (frame);
648 g_assert (ji && !ji->is_trampoline);
649 MonoMethod *method = jinfo_get_method (ji);
651 /* Compute the native offset of the breakpoint from the ip */
652 guint32 native_offset = ip - (guint8*)ji->code_start;
654 MonoSeqPointInfo *info = NULL;
655 SeqPoint sp;
656 gboolean found_sp = mono_find_prev_seq_point_for_native_offset (mono_domain_get (), method, native_offset, &info, &sp);
657 if (!found_sp)
658 DEBUG_PRINTF (1, "Could not find SP\n");
661 GPtrArray *bp_reqs = g_ptr_array_new ();
662 mono_de_collect_breakpoints_by_sp (&sp, ji, NULL, bp_reqs);
664 if (bp_reqs->len == 0) {
665 DEBUG_PRINTF (1, "BP NOT FOUND for method %s JI %p il_offset %d\n", method->name, ji, sp.il_offset);
666 return -1;
669 if (bp_reqs->len > 1)
670 DEBUG_PRINTF (1, "Multiple breakpoints (%d) at the same location, returning the first one.", bp_reqs->len);
672 EventRequest *evt = (EventRequest *)g_ptr_array_index (bp_reqs, 0);
673 g_ptr_array_free (bp_reqs, TRUE);
675 DEBUG_PRINTF (1, "Found BP %p with id %d\n", evt, evt->id);
676 return evt->id;
679 static MonoObject*
680 get_object_from_id (int objectId)
682 ObjRef *ref = (ObjRef *)g_hash_table_lookup (objrefs, GINT_TO_POINTER (objectId));
683 if (!ref) {
684 DEBUG_PRINTF (2, "get_object_from_id !ref: %d\n", objectId);
685 return NULL;
688 MonoObject *obj = mono_gchandle_get_target_internal (ref->handle);
689 if (!obj)
690 DEBUG_PRINTF (2, "get_object_from_id !obj: %d\n", objectId);
692 return obj;
695 static gboolean
696 list_frames (MonoStackFrameInfo *info, MonoContext *ctx, gpointer data)
698 SeqPoint sp;
699 MonoMethod *method;
700 char *method_full_name;
702 int* frame_id_p = (int*)data;
703 (*frame_id_p)++;
705 //skip wrappers
706 if (info->type != FRAME_TYPE_MANAGED && info->type != FRAME_TYPE_INTERP)
707 return FALSE;
709 if (info->ji)
710 method = jinfo_get_method (info->ji);
711 else
712 method = info->method;
714 if (!method || method->wrapper_type != MONO_WRAPPER_NONE)
715 return FALSE;
717 DEBUG_PRINTF (2, "list_frames: Reporting method %s native_offset %d, wrapper_type: %d\n", method->name, info->native_offset, method->wrapper_type);
719 if (!mono_find_prev_seq_point_for_native_offset (mono_get_root_domain (), method, info->native_offset, NULL, &sp))
720 DEBUG_PRINTF (2, "list_frames: Failed to lookup sequence point. method: %s, native_offset: %d\n", method->name, info->native_offset);
722 method_full_name = mono_method_full_name (method, FALSE);
723 while (method->is_inflated)
724 method = ((MonoMethodInflated*)method)->declaring;
726 char *assembly_name = g_strdup (m_class_get_image (method->klass)->module_name);
727 inplace_tolower (assembly_name);
729 DEBUG_PRINTF (2, "adding off %d token %d assembly name %s\n", sp.il_offset, mono_metadata_token_index (method->token), assembly_name);
730 mono_wasm_add_frame (sp.il_offset, mono_metadata_token_index (method->token), *frame_id_p, assembly_name, method_full_name);
732 g_free (assembly_name);
734 return FALSE;
737 EMSCRIPTEN_KEEPALIVE void
738 mono_wasm_enum_frames (void)
740 int frame_id = -1;
741 mono_walk_stack_with_ctx (list_frames, NULL, MONO_UNWIND_NONE, &frame_id);
744 static char*
745 invoke_to_string (const char *class_name, MonoClass *klass, gpointer addr)
747 MonoObject *exc;
748 MonoString *mstr;
749 char *ret_str;
750 ERROR_DECL (error);
751 MonoObject *obj;
753 // TODO: this is for a specific use case right now,
754 // (invoke ToString() get a preview/description for *some* types)
755 // and we don't want to report errors for that.
756 if (m_class_is_valuetype (klass)) {
757 MonoMethod *method;
759 MONO_STATIC_POINTER_INIT (MonoMethod, to_string)
760 to_string = mono_class_get_method_from_name_checked (mono_get_object_class (), "ToString", 0, METHOD_ATTRIBUTE_VIRTUAL | METHOD_ATTRIBUTE_PUBLIC, error);
761 mono_error_assert_ok (error);
762 MONO_STATIC_POINTER_INIT_END (MonoMethod, to_string)
764 method = mono_class_get_virtual_method (klass, to_string, FALSE, error);
765 if (!method)
766 return NULL;
768 MonoString *mstr = (MonoString*) mono_runtime_try_invoke (method, addr , NULL, &exc, error);
769 if (exc || !is_ok (error)) {
770 DEBUG_PRINTF (1, "Failed to invoke ToString for %s\n", class_name);
771 return NULL;
774 return mono_string_to_utf8_checked_internal (mstr, error);
777 obj = *(MonoObject**)addr;
778 if (!obj)
779 return NULL;
781 mstr = mono_object_try_to_string (obj, &exc, error);
782 if (exc || !is_ok (error))
783 return NULL;
785 ret_str = mono_string_to_utf8_checked_internal (mstr, error);
786 if (!is_ok (error))
787 return NULL;
789 return ret_str;
792 static char*
793 get_to_string_description (const char* class_name, MonoClass *klass, gpointer addr)
795 if (!class_name || !klass || !addr)
796 return NULL;
798 if (strcmp (class_name, "System.Guid") == 0)
799 return mono_guid_to_string (addr);
801 for (int i = 0; i < G_N_ELEMENTS (to_string_as_descr_names); i ++) {
802 if (strcmp (to_string_as_descr_names [i], class_name) == 0) {
803 return invoke_to_string (class_name, klass, addr);
807 return NULL;
810 typedef struct {
811 int cur_frame;
812 int target_frame;
813 int len;
814 int *pos;
815 gboolean found;
816 } FrameDescData;
819 * this returns a string formatted like
821 * <ret_type>:[<comma separated list of arg types>]:<method name>
823 * .. which is consumed by `mono_wasm_add_func_var`. It is used for
824 * generating this for the delegate, and it's target.
826 static char*
827 mono_method_to_desc_for_js (MonoMethod *method, gboolean include_namespace)
829 MonoMethodSignature *sig = mono_method_signature_internal (method);
830 char *ret_desc = mono_type_full_name (sig->ret);
831 char *args_desc = mono_signature_get_desc (sig, include_namespace);
833 char *sig_desc = g_strdup_printf ("%s:%s:%s", ret_desc, args_desc, method->name);
835 g_free (ret_desc);
836 g_free (args_desc);
837 return sig_desc;
840 static guint64
841 read_enum_value (const char *mem, int type)
843 switch (type) {
844 case MONO_TYPE_BOOLEAN:
845 case MONO_TYPE_U1:
846 return *(guint8*)mem;
847 case MONO_TYPE_I1:
848 return *(gint8*)mem;
849 case MONO_TYPE_CHAR:
850 case MONO_TYPE_U2:
851 return read16 (mem);
852 case MONO_TYPE_I2:
853 return (gint16) read16 (mem);
854 case MONO_TYPE_U4:
855 case MONO_TYPE_R4:
856 return read32 (mem);
857 case MONO_TYPE_I4:
858 return (gint32) read32 (mem);
859 case MONO_TYPE_U8:
860 case MONO_TYPE_I8:
861 case MONO_TYPE_R8:
862 return read64 (mem);
863 case MONO_TYPE_U:
864 case MONO_TYPE_I:
865 #if SIZEOF_REGISTER == 8
866 return read64 (mem);
867 #else
868 return read32 (mem);
869 #endif
870 default:
871 g_assert_not_reached ();
873 return 0;
876 static gboolean
877 nullable_try_get_value (guint8 *nullable, MonoClass *klass, gpointer* out_value)
879 mono_class_setup_fields (klass);
880 g_assert (m_class_is_fields_inited (klass));
882 *out_value = NULL;
883 MonoClassField *klass_fields = m_class_get_fields (klass);
884 gpointer addr_for_has_value = mono_vtype_get_field_addr (nullable, &klass_fields[0]);
885 if (0 == *(guint8*)addr_for_has_value)
886 return FALSE;
888 *out_value = mono_vtype_get_field_addr (nullable, &klass_fields[1]);
889 return TRUE;
892 static gboolean
893 describe_value(MonoType * type, gpointer addr, int gpflags)
895 ERROR_DECL (error);
896 switch (type->type) {
897 case MONO_TYPE_BOOLEAN:
898 mono_wasm_add_typed_value ("bool", NULL, *(gint8*)addr);
899 break;
900 case MONO_TYPE_I1:
901 mono_wasm_add_typed_value ("number", NULL, *(gint8*)addr);
902 break;
903 case MONO_TYPE_U1:
904 mono_wasm_add_typed_value ("number", NULL, *(guint8*)addr);
905 break;
906 case MONO_TYPE_CHAR:
907 mono_wasm_add_typed_value ("char", NULL, *(guint16*)addr);
908 break;
909 case MONO_TYPE_U2:
910 mono_wasm_add_typed_value ("number", NULL, *(guint16*)addr);
911 break;
912 case MONO_TYPE_I2:
913 mono_wasm_add_typed_value ("number", NULL, *(gint16*)addr);
914 break;
915 case MONO_TYPE_I4:
916 case MONO_TYPE_I:
917 mono_wasm_add_typed_value ("number", NULL, *(gint32*)addr);
918 break;
919 case MONO_TYPE_U4:
920 case MONO_TYPE_U:
921 mono_wasm_add_typed_value ("number", NULL, *(guint32*)addr);
922 break;
923 case MONO_TYPE_I8:
924 mono_wasm_add_typed_value ("number", NULL, *(gint64*)addr);
925 break;
926 case MONO_TYPE_U8:
927 mono_wasm_add_typed_value ("number", NULL, *(guint64*)addr);
928 break;
929 case MONO_TYPE_R4:
930 mono_wasm_add_typed_value ("number", NULL, *(float*)addr);
931 break;
932 case MONO_TYPE_R8:
933 mono_wasm_add_typed_value ("number", NULL, *(double*)addr);
934 break;
935 case MONO_TYPE_PTR:
936 case MONO_TYPE_FNPTR: {
937 char *class_name = mono_type_full_name (type);
938 const void *val = *(const void **)addr;
939 char *descr = g_strdup_printf ("(%s) %p", class_name, val);
941 EM_ASM ({
942 MONO.mono_wasm_add_typed_value ('pointer', $0, { ptr_addr: $1, klass_addr: $2 });
943 }, descr, val ? addr : 0, val ? mono_class_from_mono_type_internal (type) : 0);
945 g_free (descr);
946 g_free (class_name);
947 break;
950 case MONO_TYPE_STRING: {
951 MonoString *str_obj = *(MonoString **)addr;
952 if (!str_obj) {
953 mono_wasm_add_typed_value ("string", NULL, 0);
954 } else {
955 char *str = mono_string_to_utf8_checked_internal (str_obj, error);
956 mono_error_assert_ok (error); /* FIXME report error */
957 mono_wasm_add_typed_value ("string", str, 0);
958 g_free (str);
960 break;
963 case MONO_TYPE_OBJECT: {
964 MonoObject *obj = *(MonoObject**)addr;
965 MonoClass *klass = obj->vtable->klass;
966 if (!klass) {
967 // boxed null
968 mono_wasm_add_obj_var ("object", NULL, 0);
969 break;
972 type = m_class_get_byval_arg (klass);
973 if (type->type == MONO_TYPE_OBJECT) {
974 mono_wasm_add_obj_var ("object", "object", get_object_id (obj));
975 break;
978 // Boxed valuetype
979 if (m_class_is_valuetype (klass))
980 addr = mono_object_unbox_internal (obj);
982 return describe_value (type, addr, gpflags);
985 case MONO_TYPE_GENERICINST: {
986 MonoClass *klass = mono_class_from_mono_type_internal (type);
987 if (mono_class_is_nullable (klass)) {
988 MonoType *targ = type->data.generic_class->context.class_inst->type_argv [0];
990 gpointer nullable_value = NULL;
991 if (nullable_try_get_value (addr, klass, &nullable_value)) {
992 return describe_value (targ, nullable_value, gpflags);
993 } else {
994 char* class_name = mono_type_full_name (type);
995 mono_wasm_add_obj_var (class_name, NULL, 0);
996 g_free (class_name);
997 break;
1001 if (mono_type_generic_inst_is_valuetype (type))
1002 goto handle_vtype;
1004 * else fallthrough
1008 case MONO_TYPE_SZARRAY:
1009 case MONO_TYPE_ARRAY:
1010 case MONO_TYPE_CLASS: {
1011 MonoObject *obj = *(MonoObject**)addr;
1012 MonoClass *klass = type->data.klass;
1014 if (m_class_is_valuetype (mono_object_class (obj))) {
1015 addr = mono_object_unbox_internal (obj);
1016 type = m_class_get_byval_arg (mono_object_class (obj));
1017 goto handle_vtype;
1020 char *class_name = mono_type_full_name (type);
1021 int obj_id = get_object_id (obj);
1023 if (type-> type == MONO_TYPE_ARRAY || type->type == MONO_TYPE_SZARRAY) {
1024 MonoArray *array = (MonoArray *)obj;
1025 EM_ASM ({
1026 MONO.mono_wasm_add_typed_value ('array', $0, { objectId: $1, length: $2 });
1027 }, class_name, obj_id, mono_array_length_internal (array));
1028 } else if (m_class_is_delegate (klass) || (type->type == MONO_TYPE_GENERICINST && m_class_is_delegate (type->data.generic_class->container_class))) {
1029 MonoMethod *method;
1031 if (type->type == MONO_TYPE_GENERICINST)
1032 klass = type->data.generic_class->container_class;
1034 method = mono_get_delegate_invoke_internal (klass);
1035 if (!method) {
1036 mono_wasm_add_func_var (class_name, NULL, -1);
1037 } else {
1038 MonoMethod *tm = ((MonoDelegate *)obj)->method;
1039 char *tm_desc = NULL;
1040 if (tm)
1041 tm_desc = mono_method_to_desc_for_js (tm, FALSE);
1043 mono_wasm_add_func_var (class_name, tm_desc, obj_id);
1044 g_free (tm_desc);
1046 } else {
1047 char *to_string_val = get_to_string_description (class_name, klass, addr);
1048 mono_wasm_add_obj_var (class_name, to_string_val, obj_id);
1049 g_free (to_string_val);
1051 g_free (class_name);
1052 break;
1055 handle_vtype:
1056 case MONO_TYPE_VALUETYPE: {
1057 g_assert (addr);
1058 MonoClass *klass = mono_class_from_mono_type_internal (type);
1059 char *class_name = mono_type_full_name (type);
1061 if (m_class_is_enumtype (klass)) {
1062 MonoClassField *field;
1063 gpointer iter = NULL;
1064 const char *p;
1065 MonoTypeEnum def_type;
1066 guint64 field_value;
1067 guint64 value__ = 0xDEAD;
1068 GString *enum_members = g_string_new ("");
1069 int base_type = mono_class_enum_basetype_internal (klass)->type;
1071 while ((field = mono_class_get_fields_internal (klass, &iter))) {
1072 if (strcmp ("value__", mono_field_get_name (field)) == 0) {
1073 value__ = read_enum_value (mono_vtype_get_field_addr (addr, field), base_type);
1074 continue;
1077 if (!(field->type->attrs & FIELD_ATTRIBUTE_STATIC))
1078 continue;
1079 if (mono_field_is_deleted (field))
1080 continue;
1082 p = mono_class_get_field_default_value (field, &def_type);
1083 /* this is to correctly increment `p` in the blob */
1084 /* len = */ mono_metadata_decode_blob_size (p, &p);
1086 field_value = read_enum_value (p, base_type);
1088 g_string_append_printf (enum_members, ",%s:%llu", mono_field_get_name (field), field_value);
1091 mono_wasm_add_enum_var (class_name, enum_members->str, value__);
1092 g_string_free (enum_members, TRUE);
1093 } else {
1094 char *to_string_val = get_to_string_description (class_name, klass, addr);
1096 if (gpflags & GPFLAG_EXPAND_VALUETYPES) {
1097 int32_t size = mono_class_value_size (klass, NULL);
1098 void *value_buf = g_malloc0 (size);
1099 mono_value_copy_internal (value_buf, addr, klass);
1101 EM_ASM ({
1102 MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2, value_addr: $3, value_size: $4, klass: $5 });
1103 }, "begin_vt", class_name, to_string_val, value_buf, size, klass);
1105 g_free (value_buf);
1107 // FIXME: isAsyncLocalThis
1108 describe_object_properties_for_klass (addr, klass, FALSE, gpflags);
1109 mono_wasm_add_typed_value ("end_vt", NULL, 0);
1110 } else {
1111 EM_ASM ({
1112 MONO.mono_wasm_add_typed_value ($0, $1, { toString: $2 });
1113 }, "unexpanded_vt", class_name, to_string_val);
1115 g_free (to_string_val);
1117 g_free (class_name);
1118 break;
1120 default: {
1121 char *type_name = mono_type_full_name (type);
1122 char *msg = g_strdup_printf("can't handle type %s [%p, %x]", type_name, type, type->type);
1123 mono_wasm_add_typed_value ("string", msg, 0);
1124 g_free (msg);
1125 g_free (type_name);
1128 return TRUE;
1131 static gboolean
1132 are_getters_allowed (const char *class_name)
1134 for (int i = 0; i < G_N_ELEMENTS (all_getters_allowed_class_names); i ++) {
1135 if (strcmp (class_name, all_getters_allowed_class_names [i]) == 0)
1136 return TRUE;
1139 return FALSE;
1142 static gboolean
1143 invoke_and_describe_getter_value (MonoObject *obj, MonoProperty *p)
1145 ERROR_DECL (error);
1146 MonoObject *res;
1147 MonoObject *exc;
1149 MonoMethodSignature *sig = mono_method_signature_internal (p->get);
1151 res = mono_runtime_try_invoke (p->get, obj, NULL, &exc, error);
1152 if (!is_ok (error) && exc == NULL)
1153 exc = (MonoObject*) mono_error_convert_to_exception (error);
1154 if (exc)
1155 return describe_value (mono_get_object_type (), &exc, GPFLAG_EXPAND_VALUETYPES);
1156 else if (!res || !m_class_is_valuetype (mono_object_class (res)))
1157 return describe_value (sig->ret, &res, GPFLAG_EXPAND_VALUETYPES);
1158 else
1159 return describe_value (sig->ret, mono_object_unbox_internal (res), GPFLAG_EXPAND_VALUETYPES);
1162 static void
1163 describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags)
1165 MonoClassField *f;
1166 MonoProperty *p;
1167 MonoMethodSignature *sig;
1168 gboolean is_valuetype;
1169 int pnum;
1170 char *klass_name;
1171 gboolean auto_invoke_getters;
1172 gboolean is_own;
1173 gboolean only_backing_fields;
1175 g_assert (klass);
1176 MonoClass *start_klass = klass;
1178 only_backing_fields = gpflags & GPFLAG_ACCESSORS_ONLY;
1179 is_valuetype = m_class_is_valuetype(klass);
1180 if (is_valuetype)
1181 gpflags |= GPFLAG_EXPAND_VALUETYPES;
1183 handle_parent:
1184 is_own = (start_klass == klass);
1185 klass_name = mono_class_full_name (klass);
1186 gpointer iter = NULL;
1187 while (obj && (f = mono_class_get_fields_internal (klass, &iter))) {
1188 if (isAsyncLocalThis && f->name[0] == '<' && f->name[1] == '>') {
1189 if (g_str_has_suffix (f->name, "__this")) {
1190 mono_wasm_add_properties_var ("this", f->offset);
1191 gpointer field_value = (guint8*)obj + f->offset;
1193 describe_value (f->type, field_value, gpflags);
1196 continue;
1198 if (f->type->attrs & FIELD_ATTRIBUTE_STATIC)
1199 continue;
1200 if (mono_field_is_deleted (f))
1201 continue;
1203 if (only_backing_fields && !g_str_has_suffix(f->name, "k__BackingField"))
1204 continue;
1206 EM_ASM ({
1207 MONO.mono_wasm_add_properties_var ($0, { field_offset: $1, is_own: $2, attr: $3, owner_class: $4 });
1208 }, f->name, f->offset, is_own, f->type->attrs, klass_name);
1210 gpointer field_addr;
1211 if (is_valuetype)
1212 field_addr = mono_vtype_get_field_addr (obj, f);
1213 else
1214 field_addr = (guint8*)obj + f->offset;
1216 describe_value (f->type, field_addr, gpflags);
1219 auto_invoke_getters = are_getters_allowed (klass_name);
1220 iter = NULL;
1221 pnum = 0;
1222 while ((p = mono_class_get_properties (klass, &iter))) {
1223 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
1224 if (isAsyncLocalThis && (p->name[0] != '<' || (p->name[0] == '<' && p->name[1] == '>')))
1225 continue;
1227 sig = mono_method_signature_internal (p->get);
1228 if (sig->param_count != 0) {
1229 // getters with params are not shown
1230 continue;
1233 EM_ASM ({
1234 MONO.mono_wasm_add_properties_var ($0, { field_offset: $1, is_own: $2, attr: $3, owner_class: $4 });
1235 }, p->name, pnum, is_own, p->attrs, klass_name);
1237 gboolean vt_self_type_getter = is_valuetype && mono_class_from_mono_type_internal (sig->ret) == klass;
1238 if (auto_invoke_getters && !vt_self_type_getter) {
1239 invoke_and_describe_getter_value (obj, p);
1240 } else {
1241 // not allowed to call the getter here
1242 char *ret_class_name = mono_class_full_name (mono_class_from_mono_type_internal (sig->ret));
1244 mono_wasm_add_typed_value ("getter", ret_class_name, -1);
1246 g_free (ret_class_name);
1247 continue;
1250 pnum ++;
1253 g_free (klass_name);
1255 // ownProperties
1256 // Note: ownProperties should mean that we return members of the klass itself,
1257 // but we are going to ignore that here, because otherwise vscode/chrome don't
1258 // seem to ask for inherited fields at all.
1259 // if (!is_valuetype && !(gpflags & GPFLAG_OWN_PROPERTIES) && (klass = m_class_get_parent (klass)))
1260 if (!is_valuetype && (klass = m_class_get_parent (klass)))
1261 goto handle_parent;
1265 * We return a `Target` property only for now.
1266 * In future, we could add a `MethodInfo` too.
1268 static gboolean
1269 describe_delegate_properties (MonoObject *obj)
1271 MonoClass *klass = mono_object_class(obj);
1272 if (!m_class_is_delegate (klass))
1273 return FALSE;
1275 // Target, like in VS - what is this field supposed to be, anyway??
1276 MonoMethod *tm = ((MonoDelegate *)obj)->method;
1277 char * sig_desc = mono_method_to_desc_for_js (tm, FALSE);
1279 mono_wasm_add_properties_var ("Target", -1);
1280 mono_wasm_add_func_var (NULL, sig_desc, -1);
1282 g_free (sig_desc);
1283 return TRUE;
1286 static gboolean
1287 describe_object_properties (guint64 objectId, gboolean isAsyncLocalThis, int gpflags)
1289 DEBUG_PRINTF (2, "describe_object_properties %llu, gpflags: %d\n", objectId, gpflags);
1291 MonoObject *obj = get_object_from_id (objectId);
1292 if (!obj)
1293 return FALSE;
1295 if (m_class_is_delegate (mono_object_class (obj))) {
1296 // delegates get the same id format as regular objects
1297 describe_delegate_properties (obj);
1298 } else {
1299 describe_object_properties_for_klass (obj, obj->vtable->klass, isAsyncLocalThis, gpflags);
1302 return TRUE;
1305 static gboolean
1306 invoke_getter (void *obj_or_value, MonoClass *klass, const char *name)
1308 if (!obj_or_value || !klass || !name) {
1309 DEBUG_PRINTF (2, "invoke_getter: none of the arguments can be null");
1310 return FALSE;
1313 gpointer iter;
1314 handle_parent:
1315 iter = NULL;
1316 MonoProperty *p;
1317 while ((p = mono_class_get_properties (klass, &iter))) {
1318 //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
1319 if (!p->get->name || strcasecmp (p->name, name) != 0)
1320 continue;
1322 invoke_and_describe_getter_value (obj_or_value, p);
1323 return TRUE;
1326 if ((klass = m_class_get_parent(klass)))
1327 goto handle_parent;
1329 return FALSE;
1332 static gboolean
1333 describe_array_values (guint64 objectId, int startIdx, int count, int gpflags)
1335 if (count == 0)
1336 return TRUE;
1338 int esize;
1339 gpointer elem;
1340 MonoArray *arr = (MonoArray*) get_object_from_id (objectId);
1341 if (!arr)
1342 return FALSE;
1344 MonoClass *klass = mono_object_class (arr);
1345 MonoTypeEnum type = m_class_get_byval_arg (klass)->type;
1346 if (type != MONO_TYPE_SZARRAY && type != MONO_TYPE_ARRAY) {
1347 DEBUG_PRINTF (1, "describe_array_values: object is not an array. type: 0x%x\n", type);
1348 return FALSE;
1351 int len = arr->max_length;
1352 if (len == 0 && startIdx == 0 && count <= 0) {
1353 // Nothing to do
1354 return TRUE;
1357 if (startIdx < 0 || (len > 0 && startIdx >= len)) {
1358 DEBUG_PRINTF (1, "describe_array_values: invalid startIdx (%d) for array of length %d\n", startIdx, len);
1359 return FALSE;
1362 if (count > 0 && (startIdx + count) > len) {
1363 DEBUG_PRINTF (1, "describe_array_values: invalid count (%d) for startIdx: %d, and array of length %d\n", count, startIdx, len);
1364 return FALSE;
1367 esize = mono_array_element_size (klass);
1368 int endIdx = count < 0 ? len : startIdx + count;
1370 for (int i = startIdx; i < endIdx; i ++) {
1371 mono_wasm_add_array_item(i);
1372 elem = (gpointer*)((char*)arr->vector + (i * esize));
1373 describe_value (m_class_get_byval_arg (m_class_get_element_class (klass)), elem, gpflags);
1375 return TRUE;
1378 static void
1379 describe_async_method_locals (InterpFrame *frame, MonoMethod *method)
1381 //Async methods are special in the way that local variables can be lifted to generated class fields
1382 gpointer addr = NULL;
1383 if (mono_debug_lookup_method_async_debug_info (method)) {
1384 addr = mini_get_interp_callbacks ()->frame_get_this (frame);
1385 MonoObject *obj = *(MonoObject**)addr;
1386 int objId = get_object_id (obj);
1387 mono_wasm_set_is_async_method (objId);
1388 describe_object_properties (objId, TRUE, GPFLAG_NONE);
1392 static void
1393 describe_non_async_this (InterpFrame *frame, MonoMethod *method)
1395 gpointer addr = NULL;
1396 if (mono_debug_lookup_method_async_debug_info (method))
1397 return;
1399 if (mono_method_signature_internal (method)->hasthis) {
1400 addr = mini_get_interp_callbacks ()->frame_get_this (frame);
1401 MonoObject *obj = *(MonoObject**)addr;
1402 MonoClass *klass = method->klass;
1403 MonoType *type = m_class_get_byval_arg (method->klass);
1405 mono_wasm_add_properties_var ("this", -1);
1407 if (m_class_is_valuetype (klass)) {
1408 describe_value (type, obj, GPFLAG_EXPAND_VALUETYPES);
1409 } else {
1410 // this is an object, and we can retrieve the valuetypes in it later
1411 // through the object id
1412 describe_value (type, addr, GPFLAG_NONE);
1417 static gboolean
1418 describe_variable (InterpFrame *frame, MonoMethod *method, MonoMethodHeader *header, int pos, int gpflags)
1420 MonoType *type = NULL;
1421 gpointer addr = NULL;
1422 if (pos < 0) {
1423 MonoMethodSignature *sig = mono_method_signature_internal (method);
1424 pos = -pos - 1;
1426 if (pos >= sig->param_count) {
1427 DEBUG_PRINTF(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);
1428 return FALSE;
1431 type = sig->params [pos];
1432 addr = mini_get_interp_callbacks ()->frame_get_arg (frame, pos);
1433 } else {
1434 if (pos >= header->num_locals) {
1435 DEBUG_PRINTF(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);
1436 return FALSE;
1439 type = header->locals [pos];
1440 addr = mini_get_interp_callbacks ()->frame_get_local (frame, pos);
1443 DEBUG_PRINTF (2, "adding val %p type [%p] %s\n", addr, type, mono_type_full_name (type));
1445 return describe_value(type, addr, gpflags);
1448 static gboolean
1449 describe_variables_on_frame (MonoStackFrameInfo *info, MonoContext *ctx, gpointer ud)
1451 ERROR_DECL (error);
1452 FrameDescData *data = (FrameDescData*)ud;
1454 ++data->cur_frame;
1456 //skip wrappers
1457 if (info->type != FRAME_TYPE_MANAGED && info->type != FRAME_TYPE_INTERP) {
1458 return FALSE;
1461 if (data->cur_frame != data->target_frame)
1462 return FALSE;
1464 data->found = TRUE;
1466 InterpFrame *frame = (InterpFrame*)info->interp_frame;
1467 g_assert (frame);
1468 MonoMethod *method = frame->imethod->method;
1469 g_assert (method);
1471 MonoMethodHeader *header = mono_method_get_header_checked (method, error);
1472 mono_error_assert_ok (error); /* FIXME report error */
1474 for (int i = 0; i < data->len; i++)
1476 if (!describe_variable (frame, method, header, data->pos[i], GPFLAG_EXPAND_VALUETYPES))
1477 mono_wasm_add_typed_value("symbol", "<unreadable value>", 0);
1480 describe_async_method_locals (frame, method);
1481 describe_non_async_this (frame, method);
1483 mono_metadata_free_mh (header);
1484 return TRUE;
1487 EMSCRIPTEN_KEEPALIVE gboolean
1488 mono_wasm_get_deref_ptr_value (void *value_addr, MonoClass *klass)
1490 MonoType *type = m_class_get_byval_arg (klass);
1491 if (type->type != MONO_TYPE_PTR && type->type != MONO_TYPE_FNPTR) {
1492 DEBUG_PRINTF (2, "BUG: mono_wasm_get_deref_ptr_value: Expected to get a ptr type, but got 0x%x\n", type->type);
1493 return FALSE;
1496 mono_wasm_add_properties_var ("deref", -1);
1497 return describe_value (type->data.type, value_addr, GPFLAG_EXPAND_VALUETYPES);
1500 //FIXME this doesn't support getting the return value pseudo-var
1501 EMSCRIPTEN_KEEPALIVE gboolean
1502 mono_wasm_get_local_vars (int scope, int* pos, int len)
1504 if (scope < 0)
1505 return FALSE;
1507 FrameDescData data;
1508 data.target_frame = scope;
1509 data.cur_frame = -1;
1510 data.len = len;
1511 data.pos = pos;
1512 data.found = FALSE;
1514 mono_walk_stack_with_ctx (describe_variables_on_frame, NULL, MONO_UNWIND_NONE, &data);
1516 return data.found;
1519 EMSCRIPTEN_KEEPALIVE gboolean
1520 mono_wasm_get_object_properties (int object_id, int gpflags)
1522 DEBUG_PRINTF (2, "getting properties of object %d, gpflags: %d\n", object_id, gpflags);
1524 return describe_object_properties (object_id, FALSE, gpflags);
1527 EMSCRIPTEN_KEEPALIVE gboolean
1528 mono_wasm_get_array_values (int object_id, int start_idx, int count, int gpflags)
1530 DEBUG_PRINTF (2, "getting array values %d, startIdx: %d, count: %d, gpflags: 0x%x\n", object_id, start_idx, count, gpflags);
1532 return describe_array_values (object_id, start_idx, count, gpflags);
1535 EMSCRIPTEN_KEEPALIVE gboolean
1536 mono_wasm_invoke_getter_on_object (int object_id, const char* name)
1538 MonoObject *obj = get_object_from_id (object_id);
1539 if (!obj)
1540 return FALSE;
1542 return invoke_getter (obj, mono_object_class (obj), name);
1545 EMSCRIPTEN_KEEPALIVE gboolean
1546 mono_wasm_invoke_getter_on_value (void *value, MonoClass *klass, const char *name)
1548 DEBUG_PRINTF (2, "mono_wasm_invoke_getter_on_value: v: %p klass: %p, name: %s\n", value, klass, name);
1549 if (!klass || !value)
1550 return FALSE;
1552 if (!m_class_is_valuetype (klass)) {
1553 DEBUG_PRINTF (2, "mono_wasm_invoke_getter_on_value: klass is not a valuetype. name: %s\n", mono_class_full_name (klass));
1554 return FALSE;
1557 return invoke_getter (value, klass, name);
1560 // Functions required by debugger-state-machine.
1561 gsize
1562 mono_debugger_tls_thread_id (DebuggerTlsData *debuggerTlsData)
1564 return 1;
1567 #else // HOST_WASM
1569 void
1570 mono_wasm_single_step_hit (void)
1574 void
1575 mono_wasm_breakpoint_hit (void)
1579 void
1580 mono_wasm_debugger_init (void)
1584 #endif // HOST_WASM