Propagate error in mono_unicode_to_external (#14879)
[mono-project.git] / mono / mini / debugger-engine.c
blobe9d7d298e1b11187bbbce447ca47aad4340480ba
1 /**
2 * \file
3 * Debugger Engine shared code.
5 * Author:
6 * Zoltan Varga (vargaz@gmail.com)
7 * Rodrigo Kumpera (kumpera@gmail.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
12 #include <config.h>
13 #include "mini-runtime.h"
15 #if !defined (DISABLE_SDB) || defined(TARGET_WASM)
17 #include <glib.h>
18 #include "seq-points.h"
19 #include "aot-runtime.h"
20 #include "debugger-engine.h"
21 #include "debugger-state-machine.h"
22 #include <mono/metadata/debug-internals.h>
24 static void mono_de_ss_start (SingleStepReq *ss_req, SingleStepArgs *ss_args);
25 static gboolean mono_de_ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, void *tls, MonoContext *ctx, MonoMethod* method);
28 static DebuggerEngineCallbacks rt_callbacks;
31 * Logging support
33 static int log_level;
34 static FILE *log_file;
36 #ifdef HOST_ANDROID
37 #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { g_print (__VA_ARGS__); } } while (0)
38 #else
39 #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { fprintf (log_file, __VA_ARGS__); fflush (log_file); } } while (0)
40 #endif
43 * Locking
45 #define dbg_lock() mono_coop_mutex_lock (&debug_mutex)
46 #define dbg_unlock() mono_coop_mutex_unlock (&debug_mutex)
47 static MonoCoopMutex debug_mutex;
49 void
50 mono_de_lock (void)
52 dbg_lock ();
55 void
56 mono_de_unlock (void)
58 dbg_unlock ();
63 * Domain support
67 /* A hash table containing all active domains */
68 /* Protected by the loader lock */
69 static GHashTable *domains;
72 static void
73 domains_init (void)
75 domains = g_hash_table_new (mono_aligned_addr_hash, NULL);
78 static void
79 domains_cleanup (void)
81 //FIXME can we safely destroy `domains`?
85 * mono_de_foreach_domain:
87 * Iterate over all domains under debugging. Caller must take the loader lock.
89 * FIXME can we move the locking to here? Callers in sdb must be properly audited.
91 void
92 mono_de_foreach_domain (GHFunc func, gpointer user_data)
94 g_hash_table_foreach (domains, func, user_data);
98 * LOCKING: Takes the loader lock
100 void
101 mono_de_domain_remove (MonoDomain *domain)
103 mono_loader_lock ();
104 g_hash_table_remove (domains, domain);
105 mono_loader_unlock ();
109 * LOCKING: Takes the loader lock
111 void
112 mono_de_domain_add (MonoDomain *domain)
114 mono_loader_lock ();
115 g_hash_table_insert (domains, domain, domain);
116 mono_loader_unlock ();
120 * BREAKPOINTS
123 /* List of breakpoints */
124 /* Protected by the loader lock */
125 static GPtrArray *breakpoints;
126 /* Maps breakpoint locations to the number of breakpoints at that location */
127 static GHashTable *bp_locs;
129 static void
130 breakpoints_init (void)
132 breakpoints = g_ptr_array_new ();
133 bp_locs = g_hash_table_new (NULL, NULL);
137 * insert_breakpoint:
139 * Insert the breakpoint described by BP into the method described by
140 * JI.
142 static void
143 insert_breakpoint (MonoSeqPointInfo *seq_points, MonoDomain *domain, MonoJitInfo *ji, MonoBreakpoint *bp, MonoError *error)
145 int count;
146 BreakpointInstance *inst;
147 SeqPointIterator it;
148 gboolean it_has_sp = FALSE;
150 if (error)
151 error_init (error);
153 mono_seq_point_iterator_init (&it, seq_points);
154 while (mono_seq_point_iterator_next (&it)) {
155 if (it.seq_point.il_offset == bp->il_offset) {
156 it_has_sp = TRUE;
157 break;
161 if (!it_has_sp) {
163 * The set of IL offsets with seq points doesn't completely match the
164 * info returned by CMD_METHOD_GET_DEBUG_INFO (#407).
166 mono_seq_point_iterator_init (&it, seq_points);
167 while (mono_seq_point_iterator_next (&it)) {
168 if (it.seq_point.il_offset != METHOD_ENTRY_IL_OFFSET &&
169 it.seq_point.il_offset != METHOD_EXIT_IL_OFFSET &&
170 it.seq_point.il_offset + 1 == bp->il_offset) {
171 it_has_sp = TRUE;
172 break;
177 if (!it_has_sp) {
178 char *s = g_strdup_printf ("Unable to insert breakpoint at %s:%ld", mono_method_full_name (jinfo_get_method (ji), TRUE), bp->il_offset);
180 mono_seq_point_iterator_init (&it, seq_points);
181 while (mono_seq_point_iterator_next (&it))
182 DEBUG_PRINTF (1, "%d\n", it.seq_point.il_offset);
184 if (error) {
185 mono_error_set_error (error, MONO_ERROR_GENERIC, "%s", s);
186 g_warning ("%s", s);
187 g_free (s);
188 return;
189 } else {
190 g_warning ("%s", s);
191 g_free (s);
192 return;
196 inst = g_new0 (BreakpointInstance, 1);
197 inst->il_offset = it.seq_point.il_offset;
198 inst->native_offset = it.seq_point.native_offset;
199 inst->ip = (guint8*)ji->code_start + it.seq_point.native_offset;
200 inst->ji = ji;
201 inst->domain = domain;
203 mono_loader_lock ();
205 g_ptr_array_add (bp->children, inst);
207 mono_loader_unlock ();
209 dbg_lock ();
210 count = GPOINTER_TO_INT (g_hash_table_lookup (bp_locs, inst->ip));
211 g_hash_table_insert (bp_locs, inst->ip, GINT_TO_POINTER (count + 1));
212 dbg_unlock ();
214 if (it.seq_point.native_offset == SEQ_POINT_NATIVE_OFFSET_DEAD_CODE) {
215 DEBUG_PRINTF (1, "[dbg] Attempting to insert seq point at dead IL offset %d, ignoring.\n", (int)bp->il_offset);
216 } else if (count == 0) {
217 if (ji->is_interp) {
218 mini_get_interp_callbacks ()->set_breakpoint (ji, inst->ip);
219 } else {
220 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
221 mono_arch_set_breakpoint (ji, inst->ip);
222 #else
223 NOT_IMPLEMENTED;
224 #endif
228 DEBUG_PRINTF (1, "[dbg] Inserted breakpoint at %s:[il=0x%x,native=0x%x] [%p](%d).\n", mono_method_full_name (jinfo_get_method (ji), TRUE), (int)it.seq_point.il_offset, (int)it.seq_point.native_offset, inst->ip, count);
231 static void
232 remove_breakpoint (BreakpointInstance *inst)
234 int count;
235 MonoJitInfo *ji = inst->ji;
236 guint8 *ip = inst->ip;
238 dbg_lock ();
239 count = GPOINTER_TO_INT (g_hash_table_lookup (bp_locs, ip));
240 g_hash_table_insert (bp_locs, ip, GINT_TO_POINTER (count - 1));
241 dbg_unlock ();
243 g_assert (count > 0);
245 if (count == 1 && inst->native_offset != SEQ_POINT_NATIVE_OFFSET_DEAD_CODE) {
246 if (ji->is_interp) {
247 mini_get_interp_callbacks ()->clear_breakpoint (ji, ip);
248 } else {
249 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
250 mono_arch_clear_breakpoint (ji, ip);
251 #else
252 NOT_IMPLEMENTED;
253 #endif
255 DEBUG_PRINTF (1, "[dbg] Clear breakpoint at %s [%p].\n", mono_method_full_name (jinfo_get_method (ji), TRUE), ip);
260 * This doesn't take any locks.
262 static inline gboolean
263 bp_matches_method (MonoBreakpoint *bp, MonoMethod *method)
265 int i;
267 if (!bp->method)
268 return TRUE;
269 if (method == bp->method)
270 return TRUE;
271 if (method->is_inflated && ((MonoMethodInflated*)method)->declaring == bp->method)
272 return TRUE;
274 if (bp->method->is_inflated && method->is_inflated) {
275 MonoMethodInflated *bpimethod = (MonoMethodInflated*)bp->method;
276 MonoMethodInflated *imethod = (MonoMethodInflated*)method;
278 /* Open generic methods should match closed generic methods of the same class */
279 if (bpimethod->declaring == imethod->declaring && bpimethod->context.class_inst == imethod->context.class_inst && bpimethod->context.method_inst && bpimethod->context.method_inst->is_open) {
280 for (i = 0; i < bpimethod->context.method_inst->type_argc; ++i) {
281 MonoType *t1 = bpimethod->context.method_inst->type_argv [i];
283 /* FIXME: Handle !mvar */
284 if (t1->type != MONO_TYPE_MVAR)
285 return FALSE;
287 return TRUE;
291 return FALSE;
295 * mono_de_add_pending_breakpoints:
297 * Insert pending breakpoints into the newly JITted method METHOD.
299 void
300 mono_de_add_pending_breakpoints (MonoMethod *method, MonoJitInfo *ji)
302 int i, j;
303 MonoSeqPointInfo *seq_points;
304 MonoDomain *domain;
306 if (!breakpoints)
307 return;
309 domain = mono_domain_get ();
311 mono_loader_lock ();
313 for (i = 0; i < breakpoints->len; ++i) {
314 MonoBreakpoint *bp = (MonoBreakpoint *)g_ptr_array_index (breakpoints, i);
315 gboolean found = FALSE;
317 if (!bp_matches_method (bp, method))
318 continue;
320 for (j = 0; j < bp->children->len; ++j) {
321 BreakpointInstance *inst = (BreakpointInstance *)g_ptr_array_index (bp->children, j);
323 if (inst->ji == ji)
324 found = TRUE;
327 if (!found) {
328 seq_points = (MonoSeqPointInfo *) ji->seq_points;
330 if (!seq_points) {
331 MonoMethod *jmethod = jinfo_get_method (ji);
332 if (jmethod->is_inflated) {
333 MonoJitInfo *seq_ji;
334 MonoMethod *declaring = mono_method_get_declaring_generic_method (jmethod);
335 mono_jit_search_all_backends_for_jit_info (domain, declaring, &seq_ji);
336 seq_points = (MonoSeqPointInfo *) seq_ji->seq_points;
340 if (!seq_points)
341 /* Could be AOT code, or above "search_all_backends" call could have failed */
342 continue;
344 insert_breakpoint (seq_points, domain, ji, bp, NULL);
348 mono_loader_unlock ();
351 static void
352 set_bp_in_method (MonoDomain *domain, MonoMethod *method, MonoSeqPointInfo *seq_points, MonoBreakpoint *bp, MonoError *error)
354 gpointer code;
355 MonoJitInfo *ji;
357 if (error)
358 error_init (error);
360 code = mono_jit_search_all_backends_for_jit_info (domain, method, &ji);
361 g_assert (ji);
363 insert_breakpoint (seq_points, domain, ji, bp, error);
366 typedef struct {
367 MonoBreakpoint *bp;
368 GPtrArray *methods;
369 GPtrArray *method_domains;
370 GPtrArray *method_seq_points;
371 } CollectDomainData;
373 static void
374 collect_domain_bp (gpointer key, gpointer value, gpointer user_data)
376 GHashTableIter iter;
377 MonoSeqPointInfo *seq_points;
378 MonoDomain *domain = (MonoDomain*)key;
379 CollectDomainData *ud = (CollectDomainData*)user_data;
380 MonoMethod *m;
382 mono_domain_lock (domain);
383 g_hash_table_iter_init (&iter, domain_jit_info (domain)->seq_points);
384 while (g_hash_table_iter_next (&iter, (void**)&m, (void**)&seq_points)) {
385 if (bp_matches_method (ud->bp, m)) {
386 /* Save the info locally to simplify the code inside the domain lock */
387 g_ptr_array_add (ud->methods, m);
388 g_ptr_array_add (ud->method_domains, domain);
389 g_ptr_array_add (ud->method_seq_points, seq_points);
392 mono_domain_unlock (domain);
395 void
396 mono_de_clear_all_breakpoints (void)
398 while (breakpoints->len)
399 mono_de_clear_breakpoint ((MonoBreakpoint*)g_ptr_array_index (breakpoints, 0));
403 * mono_de_set_breakpoint:
405 * Set a breakpoint at IL_OFFSET in METHOD.
406 * METHOD can be NULL, in which case a breakpoint is placed in all methods.
407 * METHOD can also be a generic method definition, in which case a breakpoint
408 * is placed in all instances of the method.
409 * If ERROR is non-NULL, then it is set and NULL is returnd if some breakpoints couldn't be
410 * inserted.
412 MonoBreakpoint*
413 mono_de_set_breakpoint (MonoMethod *method, long il_offset, EventRequest *req, MonoError *error)
415 MonoBreakpoint *bp;
416 MonoDomain *domain;
417 MonoMethod *m;
418 MonoSeqPointInfo *seq_points;
419 GPtrArray *methods;
420 GPtrArray *method_domains;
421 GPtrArray *method_seq_points;
422 int i;
424 if (error)
425 error_init (error);
427 // FIXME:
428 // - suspend/resume the vm to prevent code patching problems
429 // - multiple breakpoints on the same location
430 // - dynamic methods
431 // - races
433 bp = g_new0 (MonoBreakpoint, 1);
434 bp->method = method;
435 bp->il_offset = il_offset;
436 bp->req = req;
437 bp->children = g_ptr_array_new ();
439 DEBUG_PRINTF (1, "[dbg] Setting %sbreakpoint at %s:0x%x.\n", (req->event_kind == EVENT_KIND_STEP) ? "single step " : "", method ? mono_method_full_name (method, TRUE) : "<all>", (int)il_offset);
441 methods = g_ptr_array_new ();
442 method_domains = g_ptr_array_new ();
443 method_seq_points = g_ptr_array_new ();
445 mono_loader_lock ();
447 CollectDomainData user_data;
448 memset (&user_data, 0, sizeof (user_data));
449 user_data.bp = bp;
450 user_data.methods = methods;
451 user_data.method_domains = method_domains;
452 user_data.method_seq_points = method_seq_points;
453 mono_de_foreach_domain (collect_domain_bp, &user_data);
455 for (i = 0; i < methods->len; ++i) {
456 m = (MonoMethod *)g_ptr_array_index (methods, i);
457 domain = (MonoDomain *)g_ptr_array_index (method_domains, i);
458 seq_points = (MonoSeqPointInfo *)g_ptr_array_index (method_seq_points, i);
459 set_bp_in_method (domain, m, seq_points, bp, error);
462 g_ptr_array_add (breakpoints, bp);
463 mono_debugger_log_add_bp (bp, bp->method, bp->il_offset);
464 mono_loader_unlock ();
466 g_ptr_array_free (methods, TRUE);
467 g_ptr_array_free (method_domains, TRUE);
468 g_ptr_array_free (method_seq_points, TRUE);
470 if (error && !mono_error_ok (error)) {
471 mono_de_clear_breakpoint (bp);
472 return NULL;
475 return bp;
478 MonoBreakpoint *
479 mono_de_get_breakpoint_by_id (int id)
481 for (int i = 0; i < breakpoints->len; ++i) {
482 MonoBreakpoint *bp = (MonoBreakpoint *)g_ptr_array_index (breakpoints, i);
483 if (bp->req->id == id)
484 return bp;
486 return NULL;
489 void
490 mono_de_clear_breakpoint (MonoBreakpoint *bp)
492 int i;
494 // FIXME: locking, races
495 for (i = 0; i < bp->children->len; ++i) {
496 BreakpointInstance *inst = (BreakpointInstance *)g_ptr_array_index (bp->children, i);
498 remove_breakpoint (inst);
500 g_free (inst);
503 mono_loader_lock ();
504 mono_debugger_log_remove_bp (bp, bp->method, bp->il_offset);
505 g_ptr_array_remove (breakpoints, bp);
506 mono_loader_unlock ();
508 g_ptr_array_free (bp->children, TRUE);
509 g_free (bp);
512 void
513 mono_de_collect_breakpoints_by_sp (SeqPoint *sp, MonoJitInfo *ji, GPtrArray *ss_reqs, GPtrArray *bp_reqs)
515 for (int i = 0; i < breakpoints->len; ++i) {
516 MonoBreakpoint *bp = (MonoBreakpoint *)g_ptr_array_index (breakpoints, i);
518 if (!bp->method)
519 continue;
521 for (int j = 0; j < bp->children->len; ++j) {
522 BreakpointInstance *inst = (BreakpointInstance *)g_ptr_array_index (bp->children, j);
523 if (inst->ji == ji && inst->il_offset == sp->il_offset && inst->native_offset == sp->native_offset) {
524 if (bp->req->event_kind == EVENT_KIND_STEP) {
525 if (ss_reqs)
526 g_ptr_array_add (ss_reqs, bp->req);
527 } else {
528 if (bp_reqs)
529 g_ptr_array_add (bp_reqs, bp->req);
536 static void
537 breakpoints_cleanup (void)
539 int i;
541 mono_loader_lock ();
543 for (i = 0; i < breakpoints->len; ++i)
544 g_free (g_ptr_array_index (breakpoints, i));
546 g_ptr_array_free (breakpoints, TRUE);
547 g_hash_table_destroy (bp_locs);
549 breakpoints = NULL;
550 bp_locs = NULL;
552 mono_loader_unlock ();
556 * mono_de_clear_breakpoints_for_domain:
558 * Clear breakpoint instances which reference DOMAIN.
560 void
561 mono_de_clear_breakpoints_for_domain (MonoDomain *domain)
563 int i, j;
565 /* This could be called after shutdown */
566 if (!breakpoints)
567 return;
569 mono_loader_lock ();
570 for (i = 0; i < breakpoints->len; ++i) {
571 MonoBreakpoint *bp = (MonoBreakpoint *)g_ptr_array_index (breakpoints, i);
573 j = 0;
574 while (j < bp->children->len) {
575 BreakpointInstance *inst = (BreakpointInstance *)g_ptr_array_index (bp->children, j);
577 if (inst->domain == domain) {
578 remove_breakpoint (inst);
580 g_free (inst);
582 g_ptr_array_remove_index_fast (bp->children, j);
583 } else {
584 j ++;
588 mono_loader_unlock ();
591 /* Single stepping engine */
592 /* Number of single stepping operations in progress */
593 static int ss_count;
595 /* The single step request instance */
596 static SingleStepReq *the_ss_req;
599 * mono_de_start_single_stepping:
601 * Turn on single stepping. Can be called multiple times, for example,
602 * by a single step event request + a suspend.
604 void
605 mono_de_start_single_stepping (void)
607 int val = mono_atomic_inc_i32 (&ss_count);
609 if (val == 1) {
610 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
611 mono_arch_start_single_stepping ();
612 #endif
613 mini_get_interp_callbacks ()->start_single_stepping ();
617 void
618 mono_de_stop_single_stepping (void)
620 int val = mono_atomic_dec_i32 (&ss_count);
622 if (val == 0) {
623 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
624 mono_arch_stop_single_stepping ();
625 #endif
626 mini_get_interp_callbacks ()->stop_single_stepping ();
630 static MonoJitInfo*
631 get_top_method_ji (gpointer ip, MonoDomain **domain, gpointer *out_ip)
633 MonoJitInfo *ji;
635 if (out_ip)
636 *out_ip = ip;
638 ji = mini_jit_info_table_find (mono_domain_get (), (char*)ip, domain);
639 if (!ji) {
640 /* Could be an interpreter method */
642 MonoLMF *lmf = mono_get_lmf ();
643 MonoInterpFrameHandle *frame;
645 g_assert (((gsize)lmf->previous_lmf) & 2);
646 MonoLMFExt *ext = (MonoLMFExt*)lmf;
648 g_assert (ext->kind == MONO_LMFEXT_INTERP_EXIT || ext->kind == MONO_LMFEXT_INTERP_EXIT_WITH_CTX);
649 frame = (MonoInterpFrameHandle*)ext->interp_exit_data;
650 ji = mini_get_interp_callbacks ()->frame_get_jit_info (frame);
651 if (domain)
652 *domain = mono_domain_get ();
653 if (out_ip)
654 *out_ip = mini_get_interp_callbacks ()->frame_get_ip (frame);
656 return ji;
659 static void
660 no_seq_points_found (MonoMethod *method, int offset)
663 * This can happen in full-aot mode with assemblies AOTed without the 'soft-debug' option to save space.
665 printf ("Unable to find seq points for method '%s', offset 0x%x.\n", mono_method_full_name (method, TRUE), offset);
668 static const char*
669 ss_depth_to_string (StepDepth depth)
671 switch (depth) {
672 case STEP_DEPTH_OVER:
673 return "over";
674 case STEP_DEPTH_OUT:
675 return "out";
676 case STEP_DEPTH_INTO:
677 return "into";
678 default:
679 g_assert_not_reached ();
680 return NULL;
685 * ss_stop:
687 * Stop the single stepping operation given by SS_REQ.
689 static void
690 ss_stop (SingleStepReq *ss_req)
692 if (ss_req->bps) {
693 GSList *l;
695 for (l = ss_req->bps; l; l = l->next) {
696 mono_de_clear_breakpoint ((MonoBreakpoint *)l->data);
698 g_slist_free (ss_req->bps);
699 ss_req->bps = NULL;
702 ss_req->async_id = 0;
703 ss_req->async_stepout_method = NULL;
704 if (ss_req->global) {
705 mono_de_stop_single_stepping ();
706 ss_req->global = FALSE;
710 static void
711 ss_destroy (SingleStepReq *req)
713 DEBUG_PRINTF (1, "[dbg] ss_destroy.\n");
715 ss_stop (req);
717 g_free (req);
720 static SingleStepReq*
721 ss_req_acquire (void)
723 SingleStepReq *req;
725 dbg_lock ();
726 req = the_ss_req;
727 if (req)
728 req->refcount ++;
729 dbg_unlock ();
730 return req;
733 static void
734 mono_de_ss_req_release (SingleStepReq *req)
736 gboolean free = FALSE;
738 dbg_lock ();
739 g_assert (req->refcount);
740 req->refcount --;
741 if (req->refcount == 0)
742 free = TRUE;
743 dbg_unlock ();
744 if (free) {
745 if (req == the_ss_req)
746 the_ss_req = NULL;
747 ss_destroy (req);
751 void
752 mono_de_cancel_ss (void)
754 if (the_ss_req) {
755 mono_de_ss_req_release (the_ss_req);
756 the_ss_req = NULL;
761 void
762 mono_de_process_single_step (void *tls, gboolean from_signal)
764 MonoJitInfo *ji;
765 guint8 *ip;
766 GPtrArray *reqs;
767 int il_offset;
768 MonoDomain *domain;
769 MonoContext *ctx = rt_callbacks.tls_get_restore_state (tls);
770 MonoMethod *method;
771 SeqPoint sp;
772 MonoSeqPointInfo *info;
773 SingleStepReq *ss_req;
775 /* Skip the instruction causing the single step */
776 rt_callbacks.begin_single_step_processing (ctx, from_signal);
778 if (rt_callbacks.try_process_suspend (tls, ctx))
779 return;
782 * This can run concurrently with a clear_event_request () call, so needs locking/reference counts.
784 ss_req = ss_req_acquire ();
786 if (!ss_req)
787 // FIXME: A suspend race
788 return;
790 if (mono_thread_internal_current () != ss_req->thread)
791 goto exit;
793 ip = (guint8 *)MONO_CONTEXT_GET_IP (ctx);
795 ji = get_top_method_ji (ip, &domain, (gpointer*)&ip);
796 g_assert (ji && !ji->is_trampoline);
798 if (log_level > 0) {
799 DEBUG_PRINTF (1, "[%p] Single step event (depth=%s) at %s (%p)[0x%x], sp %p, last sp %p\n", (gpointer) (gsize) mono_native_thread_id_get (), ss_depth_to_string (ss_req->depth), mono_method_full_name (jinfo_get_method (ji), TRUE), MONO_CONTEXT_GET_IP (ctx), (int)((guint8*)MONO_CONTEXT_GET_IP (ctx) - (guint8*)ji->code_start), MONO_CONTEXT_GET_SP (ctx), ss_req->last_sp);
802 method = jinfo_get_method (ji);
803 g_assert (method);
805 if (method->wrapper_type && method->wrapper_type != MONO_WRAPPER_DYNAMIC_METHOD)
806 goto exit;
809 * FIXME:
810 * Stopping in memset makes half-initialized vtypes visible.
811 * Stopping in memcpy makes half-copied vtypes visible.
813 if (method->klass == mono_defaults.string_class && (!strcmp (method->name, "memset") || strstr (method->name, "memcpy")))
814 goto exit;
817 * This could be in mono_de_ss_update method, but mono_find_next_seq_point_for_native_offset is pretty expensive method,
818 * hence we prefer this check here.
820 if (ss_req->user_assemblies) {
821 gboolean found = FALSE;
822 for (int k = 0; ss_req->user_assemblies[k]; k++)
823 if (ss_req->user_assemblies[k] == m_class_get_image (method->klass)->assembly) {
824 found = TRUE;
825 break;
827 if (!found)
828 goto exit;
832 * The ip points to the instruction causing the single step event, which is before
833 * the offset recorded in the seq point map, so find the next seq point after ip.
835 if (!mono_find_next_seq_point_for_native_offset (domain, method, (guint8*)ip - (guint8*)ji->code_start, &info, &sp)) {
836 g_assert_not_reached ();
837 goto exit;
840 il_offset = sp.il_offset;
842 if (!mono_de_ss_update (ss_req, ji, &sp, tls, ctx, method))
843 goto exit;
845 /* Start single stepping again from the current sequence point */
847 SingleStepArgs args;
848 memset (&args, 0, sizeof (args));
849 args.method = method;
850 args.ctx = ctx;
851 args.tls = tls;
852 args.step_to_catch = FALSE;
853 args.sp = sp;
854 args.info = info;
855 args.frames = NULL;
856 args.nframes = 0;
857 mono_de_ss_start (ss_req, &args);
859 if ((ss_req->filter & STEP_FILTER_STATIC_CTOR) &&
860 (method->flags & METHOD_ATTRIBUTE_SPECIAL_NAME) &&
861 !strcmp (method->name, ".cctor"))
862 goto exit;
864 // FIXME: Has to lock earlier
866 reqs = g_ptr_array_new ();
868 mono_loader_lock ();
870 g_ptr_array_add (reqs, ss_req->req);
872 void *bp_events;
873 bp_events = rt_callbacks.create_breakpoint_events (reqs, NULL, ji, EVENT_KIND_BREAKPOINT);
875 g_ptr_array_free (reqs, TRUE);
877 mono_loader_unlock ();
879 rt_callbacks.process_breakpoint_events (bp_events, method, ctx, il_offset);
881 exit:
882 mono_de_ss_req_release (ss_req);
886 * mono_de_ss_update:
888 * Return FALSE if single stepping needs to continue.
890 static gboolean
891 mono_de_ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, void *tls, MonoContext *ctx, MonoMethod* method)
893 MonoDebugMethodInfo *minfo;
894 MonoDebugSourceLocation *loc = NULL;
895 gboolean hit = TRUE;
897 if ((req->filter & STEP_FILTER_STATIC_CTOR)) {
898 DbgEngineStackFrame **frames;
899 int nframes;
900 rt_callbacks.ss_calculate_framecount (tls, ctx, TRUE, &frames, &nframes);
902 gboolean ret = FALSE;
903 gboolean method_in_stack = FALSE;
905 for (int i = 0; i < nframes; i++) {
906 MonoMethod *external_method = frames [i]->method;
907 if (method == external_method)
908 method_in_stack = TRUE;
910 if (!ret) {
911 ret = (external_method->flags & METHOD_ATTRIBUTE_SPECIAL_NAME);
912 ret = ret && !strcmp (external_method->name, ".cctor");
913 ret = ret && (external_method != req->start_method);
917 if (!method_in_stack) {
918 g_printerr ("[%p] The instruction pointer of the currently executing method(%s) is not on the recorded stack. This is likely due to a runtime bug. The %d frames are as follow: \n", (gpointer)(gsize)mono_native_thread_id_get (), mono_method_full_name (method, TRUE), nframes);
919 /*DEBUG_PRINTF (1, "[%p] The instruction pointer of the currently executing method(%s) is not on the recorded stack. This is likely due to a runtime bug. The %d frames are as follow: \n", (gpointer)(gsize)mono_native_thread_id_get (), mono_method_full_name (method, TRUE), tls->frame_count);*/
921 for (int i=0; i < nframes; i++)
922 g_printerr ("\t [%p] Frame (%d / %d): %s\n", (gpointer)(gsize)mono_native_thread_id_get (), i, nframes, mono_method_full_name (frames [i]->method, TRUE));
925 rt_callbacks.ss_discard_frame_context (tls);
927 if (ret)
928 return FALSE;
931 if (req->async_stepout_method == method) {
932 DEBUG_PRINTF (1, "[%p] Breakpoint hit during async step-out at %s hit, continuing stepping out.\n", (gpointer)(gsize)mono_native_thread_id_get (), method->name);
933 return FALSE;
936 if (req->depth == STEP_DEPTH_OVER && (sp->flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK) && !(sp->flags & MONO_SEQ_POINT_FLAG_NESTED_CALL)) {
938 * These seq points are inserted by the JIT after calls, step over needs to skip them.
940 DEBUG_PRINTF (1, "[%p] Seq point at nonempty stack %x while stepping over, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
941 return FALSE;
944 if ((req->depth == STEP_DEPTH_OVER || req->depth == STEP_DEPTH_OUT) && hit && !req->async_stepout_method) {
945 gboolean is_step_out = req->depth == STEP_DEPTH_OUT;
946 int nframes;
947 rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, NULL, &nframes);
949 // Because functions can call themselves recursively, we need to make sure we're stopping at the right stack depth.
950 // In case of step out, the target is the frame *enclosing* the one where the request was made.
951 int target_frames = req->nframes + (is_step_out ? -1 : 0);
952 if (req->nframes > 0 && nframes > 0 && nframes > target_frames) {
953 /* Hit the breakpoint in a recursive call, don't halt */
954 DEBUG_PRINTF (1, "[%p] Breakpoint at lower frame while stepping %s, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), is_step_out ? "out" : "over");
955 return FALSE;
959 if (req->depth == STEP_DEPTH_INTO && req->size == STEP_SIZE_MIN && (sp->flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK) && req->start_method) {
960 int nframes;
961 rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, NULL, &nframes);
962 if (req->start_method == method && req->nframes && nframes == req->nframes) { //Check also frame count(could be recursion)
963 DEBUG_PRINTF (1, "[%p] Seq point at nonempty stack %x while stepping in, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
964 return FALSE;
968 MonoDebugMethodAsyncInfo* async_method = mono_debug_lookup_method_async_debug_info (method);
969 if (async_method) {
970 for (int i = 0; i < async_method->num_awaits; i++) {
971 if (async_method->yield_offsets[i] == sp->il_offset || async_method->resume_offsets[i] == sp->il_offset) {
972 mono_debug_free_method_async_debug_info (async_method);
973 return FALSE;
976 mono_debug_free_method_async_debug_info (async_method);
979 if (req->size != STEP_SIZE_LINE)
980 return TRUE;
982 /* Have to check whenever a different source line was reached */
983 minfo = mono_debug_lookup_method (method);
985 if (minfo)
986 loc = mono_debug_method_lookup_location (minfo, sp->il_offset);
988 if (!loc) {
989 DEBUG_PRINTF (1, "[%p] No line number info for il offset %x, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
990 req->last_method = method;
991 hit = FALSE;
992 } else if (loc && method == req->last_method && loc->row == req->last_line) {
993 int nframes;
994 rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, NULL, &nframes);
995 if (nframes == req->nframes) { // If the frame has changed we're clearly not on the same source line.
996 DEBUG_PRINTF (1, "[%p] Same source line (%d), continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), loc->row);
997 hit = FALSE;
1001 if (loc) {
1002 req->last_method = method;
1003 req->last_line = loc->row;
1004 mono_debug_free_source_location (loc);
1007 return hit;
1010 void
1011 mono_de_process_breakpoint (void *void_tls, gboolean from_signal)
1013 DebuggerTlsData *tls = (DebuggerTlsData*)void_tls;
1014 MonoJitInfo *ji;
1015 guint8 *ip;
1016 int i;
1017 guint32 native_offset;
1018 GPtrArray *bp_reqs, *ss_reqs_orig, *ss_reqs;
1019 EventKind kind = EVENT_KIND_BREAKPOINT;
1020 MonoContext *ctx = rt_callbacks.tls_get_restore_state (tls);
1021 MonoMethod *method;
1022 MonoSeqPointInfo *info;
1023 SeqPoint sp;
1024 gboolean found_sp;
1026 if (rt_callbacks.try_process_suspend (tls, ctx))
1027 return;
1029 ip = (guint8 *)MONO_CONTEXT_GET_IP (ctx);
1031 ji = get_top_method_ji (ip, NULL, (gpointer*)&ip);
1032 g_assert (ji && !ji->is_trampoline);
1033 method = jinfo_get_method (ji);
1035 /* Compute the native offset of the breakpoint from the ip */
1036 native_offset = ip - (guint8*)ji->code_start;
1038 if (!rt_callbacks.begin_breakpoint_processing (tls, ctx, ji, from_signal))
1039 return;
1041 if (method->wrapper_type)
1042 return;
1044 bp_reqs = g_ptr_array_new ();
1045 ss_reqs = g_ptr_array_new ();
1046 ss_reqs_orig = g_ptr_array_new ();
1048 mono_loader_lock ();
1051 * The ip points to the instruction causing the breakpoint event, which is after
1052 * the offset recorded in the seq point map, so find the prev seq point before ip.
1054 found_sp = mono_find_prev_seq_point_for_native_offset (mono_domain_get (), method, native_offset, &info, &sp);
1056 if (!found_sp)
1057 no_seq_points_found (method, native_offset);
1059 g_assert (found_sp);
1061 DEBUG_PRINTF (1, "[%p] Breakpoint hit, method=%s, ip=%p, [il=0x%x,native=0x%x].\n", (gpointer) (gsize) mono_native_thread_id_get (), method->name, ip, sp.il_offset, native_offset);
1063 mono_debugger_log_bp_hit (tls, method, sp.il_offset);
1065 mono_de_collect_breakpoints_by_sp (&sp, ji, ss_reqs_orig, bp_reqs);
1067 if (bp_reqs->len == 0 && ss_reqs_orig->len == 0) {
1068 /* Maybe a method entry/exit event */
1069 if (sp.il_offset == METHOD_ENTRY_IL_OFFSET)
1070 kind = EVENT_KIND_METHOD_ENTRY;
1071 else if (sp.il_offset == METHOD_EXIT_IL_OFFSET)
1072 kind = EVENT_KIND_METHOD_EXIT;
1075 /* Process single step requests */
1076 for (i = 0; i < ss_reqs_orig->len; ++i) {
1077 EventRequest *req = (EventRequest *)g_ptr_array_index (ss_reqs_orig, i);
1078 SingleStepReq *ss_req = (SingleStepReq *)req->info;
1079 gboolean hit;
1081 //if we hit async_stepout_method, it's our no matter which thread
1082 if ((ss_req->async_stepout_method != method) && (ss_req->async_id || mono_thread_internal_current () != ss_req->thread)) {
1083 DbgEngineStackFrame **frames;
1084 int nframes;
1085 //We have different thread and we don't have async stepping in progress
1086 //it's breakpoint in parallel thread, ignore it
1087 if (ss_req->async_id == 0)
1088 continue;
1090 rt_callbacks.ss_discard_frame_context (tls);
1091 rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, &frames, &nframes);
1092 //make sure we have enough data to get current async method instance id
1093 if (nframes == 0 || !rt_callbacks.ensure_jit (frames [0]))
1094 continue;
1096 //Check method is async before calling get_this_async_id
1097 MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
1098 if (!asyncMethod)
1099 continue;
1100 else
1101 mono_debug_free_method_async_debug_info (asyncMethod);
1103 //breakpoint was hit in parallelly executing async method, ignore it
1104 if (ss_req->async_id != rt_callbacks.get_this_async_id (frames [0]))
1105 continue;
1108 //Update stepping request to new thread/frame_count that we are continuing on
1109 //so continuing with normal stepping works as expected
1110 if (ss_req->async_stepout_method || ss_req->async_id) {
1111 int nframes;
1112 rt_callbacks.ss_discard_frame_context (tls);
1113 rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, NULL, &nframes);
1114 ss_req->thread = mono_thread_internal_current ();
1115 ss_req->nframes = nframes;
1118 hit = mono_de_ss_update (ss_req, ji, &sp, tls, ctx, method);
1119 if (hit)
1120 g_ptr_array_add (ss_reqs, req);
1122 SingleStepArgs args;
1123 memset (&args, 0, sizeof (args));
1124 args.method = method;
1125 args.ctx = ctx;
1126 args.tls = tls;
1127 args.step_to_catch = FALSE;
1128 args.sp = sp;
1129 args.info = info;
1130 args.frames = NULL;
1131 args.nframes = 0;
1132 mono_de_ss_start (ss_req, &args);
1135 void *bp_events = rt_callbacks.create_breakpoint_events (ss_reqs, bp_reqs, ji, kind);
1137 mono_loader_unlock ();
1139 g_ptr_array_free (bp_reqs, TRUE);
1140 g_ptr_array_free (ss_reqs, TRUE);
1142 rt_callbacks.process_breakpoint_events (bp_events, method, ctx, 0);
1146 * ss_bp_is_unique:
1148 * Reject breakpoint if it is a duplicate of one already in list or hash table.
1150 static gboolean
1151 ss_bp_is_unique (GSList *bps, GHashTable *ss_req_bp_cache, MonoMethod *method, guint32 il_offset)
1153 if (ss_req_bp_cache) {
1154 MonoBreakpoint dummy = {method, (long)il_offset, NULL, NULL};
1155 return !g_hash_table_lookup (ss_req_bp_cache, &dummy);
1157 for (GSList *l = bps; l; l = l->next) {
1158 MonoBreakpoint *bp = (MonoBreakpoint *)l->data;
1159 if (bp->method == method && bp->il_offset == il_offset)
1160 return FALSE;
1162 return TRUE;
1166 * ss_bp_eq:
1168 * GHashTable equality for a MonoBreakpoint (only care about method and il_offset fields)
1170 static gint
1171 ss_bp_eq (gconstpointer ka, gconstpointer kb)
1173 const MonoBreakpoint *s1 = (const MonoBreakpoint *)ka;
1174 const MonoBreakpoint *s2 = (const MonoBreakpoint *)kb;
1175 return (s1->method == s2->method && s1->il_offset == s2->il_offset) ? 1 : 0;
1179 * ss_bp_eq:
1181 * GHashTable hash for a MonoBreakpoint (only care about method and il_offset fields)
1183 static guint
1184 ss_bp_hash (gconstpointer data)
1186 const MonoBreakpoint *s = (const MonoBreakpoint *)data;
1187 guint hash = (guint) (uintptr_t) s->method;
1188 hash ^= ((guint)s->il_offset) << 16; // Assume low bits are more interesting
1189 hash ^= ((guint)s->il_offset) >> 16;
1190 return hash;
1193 #define MAX_LINEAR_SCAN_BPS 7
1196 * ss_bp_add_one:
1198 * Create a new breakpoint and add it to a step request.
1199 * Will adjust the bp count and cache used by mono_de_ss_start.
1201 static void
1202 ss_bp_add_one (SingleStepReq *ss_req, int *ss_req_bp_count, GHashTable **ss_req_bp_cache,
1203 MonoMethod *method, guint32 il_offset)
1205 // This list is getting too long, switch to using the hash table
1206 if (!*ss_req_bp_cache && *ss_req_bp_count > MAX_LINEAR_SCAN_BPS) {
1207 *ss_req_bp_cache = g_hash_table_new (ss_bp_hash, ss_bp_eq);
1208 for (GSList *l = ss_req->bps; l; l = l->next)
1209 g_hash_table_insert (*ss_req_bp_cache, l->data, l->data);
1212 if (ss_bp_is_unique (ss_req->bps, *ss_req_bp_cache, method, il_offset)) {
1213 // Create and add breakpoint
1214 MonoBreakpoint *bp = mono_de_set_breakpoint (method, il_offset, ss_req->req, NULL);
1215 ss_req->bps = g_slist_append (ss_req->bps, bp);
1216 if (*ss_req_bp_cache)
1217 g_hash_table_insert (*ss_req_bp_cache, bp, bp);
1218 (*ss_req_bp_count)++;
1219 } else {
1220 DEBUG_PRINTF (1, "[dbg] Candidate breakpoint at %s:[il=0x%x] is a duplicate for this step request, will not add.\n", mono_method_full_name (method, TRUE), (int)il_offset);
1224 static gboolean
1225 is_last_non_empty (SeqPoint* sp, MonoSeqPointInfo *info)
1227 if (!sp->next_len)
1228 return TRUE;
1229 SeqPoint* next = g_new (SeqPoint, sp->next_len);
1230 mono_seq_point_init_next (info, *sp, next);
1231 for (int i = 0; i < sp->next_len; i++) {
1232 if (next [i].flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK && !(next [i].flags & MONO_SEQ_POINT_FLAG_NESTED_CALL)) {
1233 if (!is_last_non_empty (&next [i], info)) {
1234 g_free (next);
1235 return FALSE;
1237 } else {
1238 g_free (next);
1239 return FALSE;
1242 g_free (next);
1243 return TRUE;
1247 * mono_de_ss_start:
1249 * Start the single stepping operation given by SS_REQ from the sequence point SP.
1250 * If CTX is not set, then this can target any thread. If CTX is set, then TLS should
1251 * belong to the same thread as CTX.
1252 * If FRAMES is not-null, use that instead of tls->frames for placing breakpoints etc.
1254 static void
1255 mono_de_ss_start (SingleStepReq *ss_req, SingleStepArgs *ss_args)
1257 int i, j, frame_index;
1258 SeqPoint *next_sp, *parent_sp = NULL;
1259 SeqPoint local_sp, local_parent_sp;
1260 gboolean found_sp;
1261 MonoSeqPointInfo *parent_info;
1262 MonoMethod *parent_sp_method = NULL;
1263 gboolean enable_global = FALSE;
1265 // When 8 or more entries are in bps, we build a hash table to serve as a set of breakpoints.
1266 // Recreating this on each pass is a little wasteful but at least keeps behavior linear.
1267 int ss_req_bp_count = g_slist_length (ss_req->bps);
1268 GHashTable *ss_req_bp_cache = NULL;
1270 /* Stop the previous operation */
1271 ss_stop (ss_req);
1273 gboolean locked = FALSE;
1275 void *tls = ss_args->tls;
1276 MonoMethod *method = ss_args->method;
1277 DbgEngineStackFrame **frames = ss_args->frames;
1278 int nframes = ss_args->nframes;
1279 SeqPoint *sp = &ss_args->sp;
1282 * Implement single stepping using breakpoints if possible.
1284 if (ss_args->step_to_catch) {
1285 ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, method, sp->il_offset);
1286 } else {
1287 frame_index = 1;
1289 if (ss_args->ctx && !frames) {
1291 mono_loader_lock ();
1292 locked = TRUE;
1294 /* Need parent frames */
1295 rt_callbacks.ss_calculate_framecount (tls, ss_args->ctx, FALSE, &frames, &nframes);
1298 MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
1300 /* Need to stop in catch clauses as well */
1301 for (i = ss_req->depth == STEP_DEPTH_OUT ? 1 : 0; i < nframes; ++i) {
1302 DbgEngineStackFrame *frame = frames [i];
1304 if (frame->ji) {
1305 MonoJitInfo *jinfo = frame->ji;
1306 for (j = 0; j < jinfo->num_clauses; ++j) {
1307 // In case of async method we don't want to place breakpoint on last catch handler(which state machine added for whole method)
1308 if (asyncMethod && asyncMethod->num_awaits && i == 0 && j + 1 == jinfo->num_clauses)
1309 break;
1310 MonoJitExceptionInfo *ei = &jinfo->clauses [j];
1312 if (mono_find_next_seq_point_for_native_offset (frame->domain, frame->method, (char*)ei->handler_start - (char*)jinfo->code_start, NULL, &local_sp))
1313 ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, frame->method, local_sp.il_offset);
1318 if (asyncMethod && asyncMethod->num_awaits && nframes && rt_callbacks.ensure_jit (frames [0])) {
1319 //asyncMethod has value and num_awaits > 0, this means we are inside async method with awaits
1321 // Check if we hit yield_offset during normal stepping, because if we did...
1322 // Go into special async stepping mode which places breakpoint on resumeOffset
1323 // of this await call and sets async_id so we can distinguish it from parallel executions
1324 for (i = 0; i < asyncMethod->num_awaits; i++) {
1325 if (sp->il_offset == asyncMethod->yield_offsets [i]) {
1326 ss_req->async_id = rt_callbacks.get_this_async_id (frames [0]);
1327 ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, method, asyncMethod->resume_offsets [i]);
1328 g_hash_table_destroy (ss_req_bp_cache);
1329 mono_debug_free_method_async_debug_info (asyncMethod);
1330 if (locked)
1331 mono_loader_unlock ();
1332 goto cleanup;
1335 //If we are at end of async method and doing step-in or step-over...
1336 //Switch to step-out, so whole NotifyDebuggerOfWaitCompletion magic happens...
1337 if (is_last_non_empty (sp, ss_args->info)) {
1338 ss_req->depth = STEP_DEPTH_OUT;//setting depth to step-out is important, don't inline IF, because code later depends on this
1340 if (ss_req->depth == STEP_DEPTH_OUT) {
1341 //If we are inside `async void` method, do normal step-out
1342 if (rt_callbacks.set_set_notification_for_wait_completion_flag (frames [0])) {
1343 ss_req->async_id = rt_callbacks.get_this_async_id (frames [0]);
1344 ss_req->async_stepout_method = rt_callbacks.get_notify_debugger_of_wait_completion_method ();
1345 ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, ss_req->async_stepout_method, 0);
1346 g_hash_table_destroy (ss_req_bp_cache);
1347 mono_debug_free_method_async_debug_info (asyncMethod);
1348 if (locked)
1349 mono_loader_unlock ();
1350 goto cleanup;
1355 if (asyncMethod)
1356 mono_debug_free_method_async_debug_info (asyncMethod);
1359 * Find the first sequence point in the current or in a previous frame which
1360 * is not the last in its method.
1362 if (ss_req->depth == STEP_DEPTH_OUT) {
1363 /* Ignore seq points in current method */
1364 while (frame_index < nframes) {
1365 DbgEngineStackFrame *frame = frames [frame_index];
1367 method = frame->method;
1368 found_sp = mono_find_prev_seq_point_for_native_offset (frame->domain, frame->method, frame->native_offset, &ss_args->info, &local_sp);
1369 sp = (found_sp)? &local_sp : NULL;
1370 frame_index ++;
1371 if (sp && sp->next_len != 0)
1372 break;
1374 // There could be method calls before the next seq point in the caller when using nested calls
1375 //enable_global = TRUE;
1376 } else {
1377 if (sp && sp->next_len == 0) {
1378 sp = NULL;
1379 while (frame_index < nframes) {
1380 DbgEngineStackFrame *frame = frames [frame_index];
1382 method = frame->method;
1383 found_sp = mono_find_prev_seq_point_for_native_offset (frame->domain, frame->method, frame->native_offset, &ss_args->info, &local_sp);
1384 sp = (found_sp)? &local_sp : NULL;
1385 if (sp && sp->next_len != 0)
1386 break;
1387 sp = NULL;
1388 frame_index ++;
1390 } else {
1391 /* Have to put a breakpoint into a parent frame since the seq points might not cover all control flow out of the method */
1392 while (frame_index < nframes) {
1393 DbgEngineStackFrame *frame = frames [frame_index];
1395 parent_sp_method = frame->method;
1396 found_sp = mono_find_prev_seq_point_for_native_offset (frame->domain, frame->method, frame->native_offset, &parent_info, &local_parent_sp);
1397 parent_sp = found_sp ? &local_parent_sp : NULL;
1398 if (found_sp && parent_sp->next_len != 0)
1399 break;
1400 parent_sp = NULL;
1401 frame_index ++;
1406 if (sp && sp->next_len > 0) {
1407 SeqPoint* next = g_new(SeqPoint, sp->next_len);
1409 mono_seq_point_init_next (ss_args->info, *sp, next);
1410 for (i = 0; i < sp->next_len; i++) {
1411 next_sp = &next[i];
1413 ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, method, next_sp->il_offset);
1415 g_free (next);
1418 if (parent_sp) {
1419 SeqPoint* next = g_new(SeqPoint, parent_sp->next_len);
1421 mono_seq_point_init_next (parent_info, *parent_sp, next);
1422 for (i = 0; i < parent_sp->next_len; i++) {
1423 next_sp = &next[i];
1425 ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, parent_sp_method, next_sp->il_offset);
1427 g_free (next);
1430 if (ss_req->nframes == 0)
1431 ss_req->nframes = nframes;
1433 if ((ss_req->depth == STEP_DEPTH_OVER) && (!sp && !parent_sp)) {
1434 DEBUG_PRINTF (1, "[dbg] No parent frame for step over, transition to step into.\n");
1436 * This is needed since if we leave managed code, and later return to it, step over
1437 * is not going to stop.
1438 * This approach is a bit ugly, since we change the step depth, but it only affects
1439 * clients who reuse the same step request, and only in this special case.
1441 ss_req->depth = STEP_DEPTH_INTO;
1444 if (ss_req->depth == STEP_DEPTH_INTO) {
1445 /* Enable global stepping so we stop at method entry too */
1446 enable_global = TRUE;
1450 * The ctx/frame info computed above will become invalid when we continue.
1452 rt_callbacks.ss_discard_frame_context (tls);
1455 if (enable_global) {
1456 DEBUG_PRINTF (1, "[dbg] Turning on global single stepping.\n");
1457 ss_req->global = TRUE;
1458 mono_de_start_single_stepping ();
1459 } else if (!ss_req->bps) {
1460 DEBUG_PRINTF (1, "[dbg] Turning on global single stepping.\n");
1461 ss_req->global = TRUE;
1462 mono_de_start_single_stepping ();
1463 } else {
1464 ss_req->global = FALSE;
1467 g_hash_table_destroy (ss_req_bp_cache);
1469 if (locked)
1470 mono_loader_unlock ();
1472 cleanup:
1473 rt_callbacks.ss_args_destroy (ss_args);
1478 * Start single stepping of thread THREAD
1480 DbgEngineErrorCode
1481 mono_de_ss_create (MonoInternalThread *thread, StepSize size, StepDepth depth, StepFilter filter, EventRequest *req)
1483 int err = rt_callbacks.ensure_runtime_is_suspended ();
1484 if (err)
1485 return err;
1487 // FIXME: Multiple requests
1488 if (the_ss_req) {
1489 DEBUG_PRINTF (0, "Received a single step request while the previous one was still active.\n");
1490 return DE_ERR_NOT_IMPLEMENTED;
1493 DEBUG_PRINTF (1, "[dbg] Starting single step of thread %p (depth=%s).\n", thread, ss_depth_to_string (depth));
1495 SingleStepReq *ss_req = g_new0 (SingleStepReq, 1);
1496 ss_req->req = req;
1497 ss_req->thread = thread;
1498 ss_req->size = size;
1499 ss_req->depth = depth;
1500 ss_req->filter = filter;
1501 ss_req->refcount = 1;
1502 req->info = ss_req;
1504 for (int i = 0; i < req->nmodifiers; i++) {
1505 if (req->modifiers[i].kind == MOD_KIND_ASSEMBLY_ONLY) {
1506 ss_req->user_assemblies = req->modifiers[i].data.assemblies;
1507 break;
1511 SingleStepArgs args;
1512 err = rt_callbacks.ss_create_init_args (ss_req, &args);
1513 if (err)
1514 return err;
1516 the_ss_req = ss_req;
1518 mono_de_ss_start (ss_req, &args);
1520 return DE_ERR_NONE;
1524 * mono_de_set_log_level:
1526 * Configures logging level and output file. Must be called together with mono_de_init.
1528 void
1529 mono_de_set_log_level (int level, FILE *file)
1531 log_level = level;
1532 log_file = file;
1536 * mono_de_init:
1538 * Inits the shared debugger engine. Not reentrant.
1540 void
1541 mono_de_init (DebuggerEngineCallbacks *cbs)
1543 rt_callbacks = *cbs;
1544 mono_coop_mutex_init_recursive (&debug_mutex);
1546 domains_init ();
1547 breakpoints_init ();
1549 mono_debugger_log_init ();
1552 void
1553 mono_de_cleanup (void)
1555 breakpoints_cleanup ();
1556 domains_cleanup ();
1559 void
1560 mono_debugger_free_objref (gpointer value)
1562 ObjRef *o = (ObjRef *)value;
1564 mono_gchandle_free_internal (o->handle);
1566 g_free (o);
1568 #endif