3 * Debugger Engine shared code.
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.
13 #include "mini-runtime.h"
15 #if !defined (DISABLE_SDB) || defined(TARGET_WASM)
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
;
34 static FILE *log_file
;
37 #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { g_print (__VA_ARGS__); } } while (0)
39 #define DEBUG_PRINTF(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { fprintf (log_file, __VA_ARGS__); fflush (log_file); } } while (0)
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
;
67 /* A hash table containing all active domains */
68 /* Protected by the loader lock */
69 static GHashTable
*domains
;
75 domains
= g_hash_table_new (mono_aligned_addr_hash
, NULL
);
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.
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
101 mono_de_domain_remove (MonoDomain
*domain
)
104 g_hash_table_remove (domains
, domain
);
105 mono_loader_unlock ();
109 * LOCKING: Takes the loader lock
112 mono_de_domain_add (MonoDomain
*domain
)
115 g_hash_table_insert (domains
, domain
, domain
);
116 mono_loader_unlock ();
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
;
130 breakpoints_init (void)
132 breakpoints
= g_ptr_array_new ();
133 bp_locs
= g_hash_table_new (NULL
, NULL
);
139 * Insert the breakpoint described by BP into the method described by
143 insert_breakpoint (MonoSeqPointInfo
*seq_points
, MonoDomain
*domain
, MonoJitInfo
*ji
, MonoBreakpoint
*bp
, MonoError
*error
)
146 BreakpointInstance
*inst
;
148 gboolean it_has_sp
= FALSE
;
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
) {
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
) {
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
);
185 mono_error_set_error (error
, MONO_ERROR_GENERIC
, "%s", s
);
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
;
201 inst
->domain
= domain
;
205 g_ptr_array_add (bp
->children
, inst
);
207 mono_loader_unlock ();
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));
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) {
218 mini_get_interp_callbacks ()->set_breakpoint (ji
, inst
->ip
);
220 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
221 mono_arch_set_breakpoint (ji
, inst
->ip
);
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
);
232 remove_breakpoint (BreakpointInstance
*inst
)
235 MonoJitInfo
*ji
= inst
->ji
;
236 guint8
*ip
= inst
->ip
;
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));
243 g_assert (count
> 0);
245 if (count
== 1 && inst
->native_offset
!= SEQ_POINT_NATIVE_OFFSET_DEAD_CODE
) {
247 mini_get_interp_callbacks ()->clear_breakpoint (ji
, ip
);
249 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
250 mono_arch_clear_breakpoint (ji
, ip
);
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.
263 bp_matches_method (MonoBreakpoint
*bp
, MonoMethod
*method
)
269 if (method
== bp
->method
)
271 if (method
->is_inflated
&& ((MonoMethodInflated
*)method
)->declaring
== bp
->method
)
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
)
295 * mono_de_add_pending_breakpoints:
297 * Insert pending breakpoints into the newly JITted method METHOD.
300 mono_de_add_pending_breakpoints (MonoMethod
*method
, MonoJitInfo
*ji
)
303 MonoSeqPointInfo
*seq_points
;
309 domain
= mono_domain_get ();
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
))
320 for (j
= 0; j
< bp
->children
->len
; ++j
) {
321 BreakpointInstance
*inst
= (BreakpointInstance
*)g_ptr_array_index (bp
->children
, j
);
328 seq_points
= (MonoSeqPointInfo
*) ji
->seq_points
;
331 MonoMethod
*jmethod
= jinfo_get_method (ji
);
332 if (jmethod
->is_inflated
) {
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
;
341 /* Could be AOT code, or above "search_all_backends" call could have failed */
344 insert_breakpoint (seq_points
, domain
, ji
, bp
, NULL
);
348 mono_loader_unlock ();
352 set_bp_in_method (MonoDomain
*domain
, MonoMethod
*method
, MonoSeqPointInfo
*seq_points
, MonoBreakpoint
*bp
, MonoError
*error
)
359 (void)mono_jit_search_all_backends_for_jit_info (domain
, method
, &ji
);
362 insert_breakpoint (seq_points
, domain
, ji
, bp
, error
);
368 GPtrArray
*method_domains
;
369 GPtrArray
*method_seq_points
;
373 collect_domain_bp (gpointer key
, gpointer value
, gpointer user_data
)
376 MonoSeqPointInfo
*seq_points
;
377 MonoDomain
*domain
= (MonoDomain
*)key
;
378 CollectDomainData
*ud
= (CollectDomainData
*)user_data
;
381 mono_domain_lock (domain
);
382 g_hash_table_iter_init (&iter
, domain_jit_info (domain
)->seq_points
);
383 while (g_hash_table_iter_next (&iter
, (void**)&m
, (void**)&seq_points
)) {
384 if (bp_matches_method (ud
->bp
, m
)) {
385 /* Save the info locally to simplify the code inside the domain lock */
386 g_ptr_array_add (ud
->methods
, m
);
387 g_ptr_array_add (ud
->method_domains
, domain
);
388 g_ptr_array_add (ud
->method_seq_points
, seq_points
);
391 mono_domain_unlock (domain
);
395 mono_de_clear_all_breakpoints (void)
397 while (breakpoints
->len
)
398 mono_de_clear_breakpoint ((MonoBreakpoint
*)g_ptr_array_index (breakpoints
, 0));
402 * mono_de_set_breakpoint:
404 * Set a breakpoint at IL_OFFSET in METHOD.
405 * METHOD can be NULL, in which case a breakpoint is placed in all methods.
406 * METHOD can also be a generic method definition, in which case a breakpoint
407 * is placed in all instances of the method.
408 * If ERROR is non-NULL, then it is set and NULL is returnd if some breakpoints couldn't be
412 mono_de_set_breakpoint (MonoMethod
*method
, long il_offset
, EventRequest
*req
, MonoError
*error
)
417 MonoSeqPointInfo
*seq_points
;
419 GPtrArray
*method_domains
;
420 GPtrArray
*method_seq_points
;
427 // - suspend/resume the vm to prevent code patching problems
428 // - multiple breakpoints on the same location
432 bp
= g_new0 (MonoBreakpoint
, 1);
434 bp
->il_offset
= il_offset
;
436 bp
->children
= g_ptr_array_new ();
438 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
);
440 methods
= g_ptr_array_new ();
441 method_domains
= g_ptr_array_new ();
442 method_seq_points
= g_ptr_array_new ();
446 CollectDomainData user_data
;
447 memset (&user_data
, 0, sizeof (user_data
));
449 user_data
.methods
= methods
;
450 user_data
.method_domains
= method_domains
;
451 user_data
.method_seq_points
= method_seq_points
;
452 mono_de_foreach_domain (collect_domain_bp
, &user_data
);
454 for (i
= 0; i
< methods
->len
; ++i
) {
455 m
= (MonoMethod
*)g_ptr_array_index (methods
, i
);
456 domain
= (MonoDomain
*)g_ptr_array_index (method_domains
, i
);
457 seq_points
= (MonoSeqPointInfo
*)g_ptr_array_index (method_seq_points
, i
);
458 set_bp_in_method (domain
, m
, seq_points
, bp
, error
);
461 g_ptr_array_add (breakpoints
, bp
);
462 mono_debugger_log_add_bp (bp
, bp
->method
, bp
->il_offset
);
463 mono_loader_unlock ();
465 g_ptr_array_free (methods
, TRUE
);
466 g_ptr_array_free (method_domains
, TRUE
);
467 g_ptr_array_free (method_seq_points
, TRUE
);
469 if (error
&& !is_ok (error
)) {
470 mono_de_clear_breakpoint (bp
);
478 mono_de_get_breakpoint_by_id (int id
)
480 for (int i
= 0; i
< breakpoints
->len
; ++i
) {
481 MonoBreakpoint
*bp
= (MonoBreakpoint
*)g_ptr_array_index (breakpoints
, i
);
482 if (bp
->req
->id
== id
)
489 mono_de_clear_breakpoint (MonoBreakpoint
*bp
)
493 // FIXME: locking, races
494 for (i
= 0; i
< bp
->children
->len
; ++i
) {
495 BreakpointInstance
*inst
= (BreakpointInstance
*)g_ptr_array_index (bp
->children
, i
);
497 remove_breakpoint (inst
);
503 mono_debugger_log_remove_bp (bp
, bp
->method
, bp
->il_offset
);
504 g_ptr_array_remove (breakpoints
, bp
);
505 mono_loader_unlock ();
507 g_ptr_array_free (bp
->children
, TRUE
);
512 mono_de_collect_breakpoints_by_sp (SeqPoint
*sp
, MonoJitInfo
*ji
, GPtrArray
*ss_reqs
, GPtrArray
*bp_reqs
)
514 for (int i
= 0; i
< breakpoints
->len
; ++i
) {
515 MonoBreakpoint
*bp
= (MonoBreakpoint
*)g_ptr_array_index (breakpoints
, i
);
520 for (int j
= 0; j
< bp
->children
->len
; ++j
) {
521 BreakpointInstance
*inst
= (BreakpointInstance
*)g_ptr_array_index (bp
->children
, j
);
522 if (inst
->ji
== ji
&& inst
->il_offset
== sp
->il_offset
&& inst
->native_offset
== sp
->native_offset
) {
523 if (bp
->req
->event_kind
== EVENT_KIND_STEP
) {
525 g_ptr_array_add (ss_reqs
, bp
->req
);
528 g_ptr_array_add (bp_reqs
, bp
->req
);
536 breakpoints_cleanup (void)
542 for (i
= 0; i
< breakpoints
->len
; ++i
)
543 g_free (g_ptr_array_index (breakpoints
, i
));
545 g_ptr_array_free (breakpoints
, TRUE
);
546 g_hash_table_destroy (bp_locs
);
551 mono_loader_unlock ();
555 * mono_de_clear_breakpoints_for_domain:
557 * Clear breakpoint instances which reference DOMAIN.
560 mono_de_clear_breakpoints_for_domain (MonoDomain
*domain
)
564 /* This could be called after shutdown */
569 for (i
= 0; i
< breakpoints
->len
; ++i
) {
570 MonoBreakpoint
*bp
= (MonoBreakpoint
*)g_ptr_array_index (breakpoints
, i
);
573 while (j
< bp
->children
->len
) {
574 BreakpointInstance
*inst
= (BreakpointInstance
*)g_ptr_array_index (bp
->children
, j
);
576 if (inst
->domain
== domain
) {
577 remove_breakpoint (inst
);
581 g_ptr_array_remove_index_fast (bp
->children
, j
);
587 mono_loader_unlock ();
590 /* Single stepping engine */
591 /* Number of single stepping operations in progress */
594 /* The single step request instance */
595 static SingleStepReq
*the_ss_req
;
598 * mono_de_start_single_stepping:
600 * Turn on single stepping. Can be called multiple times, for example,
601 * by a single step event request + a suspend.
604 mono_de_start_single_stepping (void)
606 int val
= mono_atomic_inc_i32 (&ss_count
);
609 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
610 mono_arch_start_single_stepping ();
612 mini_get_interp_callbacks ()->start_single_stepping ();
617 mono_de_stop_single_stepping (void)
619 int val
= mono_atomic_dec_i32 (&ss_count
);
622 #ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
623 mono_arch_stop_single_stepping ();
625 mini_get_interp_callbacks ()->stop_single_stepping ();
630 get_top_method_ji (gpointer ip
, MonoDomain
**domain
, gpointer
*out_ip
)
637 ji
= mini_jit_info_table_find (mono_domain_get (), (char*)ip
, domain
);
639 /* Could be an interpreter method */
641 MonoLMF
*lmf
= mono_get_lmf ();
642 MonoInterpFrameHandle
*frame
;
644 g_assert (((gsize
)lmf
->previous_lmf
) & 2);
645 MonoLMFExt
*ext
= (MonoLMFExt
*)lmf
;
647 g_assert (ext
->kind
== MONO_LMFEXT_INTERP_EXIT
|| ext
->kind
== MONO_LMFEXT_INTERP_EXIT_WITH_CTX
);
648 frame
= (MonoInterpFrameHandle
*)ext
->interp_exit_data
;
649 ji
= mini_get_interp_callbacks ()->frame_get_jit_info (frame
);
651 *domain
= mono_domain_get ();
653 *out_ip
= mini_get_interp_callbacks ()->frame_get_ip (frame
);
659 no_seq_points_found (MonoMethod
*method
, int offset
)
662 * This can happen in full-aot mode with assemblies AOTed without the 'soft-debug' option to save space.
664 printf ("Unable to find seq points for method '%s', offset 0x%x.\n", mono_method_full_name (method
, TRUE
), offset
);
668 ss_depth_to_string (StepDepth depth
)
671 case STEP_DEPTH_OVER
:
675 case STEP_DEPTH_INTO
:
678 g_assert_not_reached ();
686 * Stop the single stepping operation given by SS_REQ.
689 ss_stop (SingleStepReq
*ss_req
)
694 for (l
= ss_req
->bps
; l
; l
= l
->next
) {
695 mono_de_clear_breakpoint ((MonoBreakpoint
*)l
->data
);
697 g_slist_free (ss_req
->bps
);
701 ss_req
->async_id
= 0;
702 ss_req
->async_stepout_method
= NULL
;
703 if (ss_req
->global
) {
704 mono_de_stop_single_stepping ();
705 ss_req
->global
= FALSE
;
710 ss_destroy (SingleStepReq
*req
)
712 DEBUG_PRINTF (1, "[dbg] ss_destroy.\n");
719 static SingleStepReq
*
720 ss_req_acquire (void)
733 mono_de_ss_req_release (SingleStepReq
*req
)
735 gboolean free
= FALSE
;
738 g_assert (req
->refcount
);
740 if (req
->refcount
== 0)
744 if (req
== the_ss_req
)
751 mono_de_cancel_ss (void)
754 mono_de_ss_req_release (the_ss_req
);
761 mono_de_process_single_step (void *tls
, gboolean from_signal
)
768 MonoContext
*ctx
= rt_callbacks
.tls_get_restore_state (tls
);
771 MonoSeqPointInfo
*info
;
772 SingleStepReq
*ss_req
;
774 /* Skip the instruction causing the single step */
775 rt_callbacks
.begin_single_step_processing (ctx
, from_signal
);
777 if (rt_callbacks
.try_process_suspend (tls
, ctx
))
781 * This can run concurrently with a clear_event_request () call, so needs locking/reference counts.
783 ss_req
= ss_req_acquire ();
786 // FIXME: A suspend race
789 if (mono_thread_internal_current () != ss_req
->thread
)
792 ip
= (guint8
*)MONO_CONTEXT_GET_IP (ctx
);
794 ji
= get_top_method_ji (ip
, &domain
, (gpointer
*)&ip
);
795 g_assert (ji
&& !ji
->is_trampoline
);
798 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
);
801 method
= jinfo_get_method (ji
);
804 if (method
->wrapper_type
&& method
->wrapper_type
!= MONO_WRAPPER_DYNAMIC_METHOD
)
809 * Stopping in memset makes half-initialized vtypes visible.
810 * Stopping in memcpy makes half-copied vtypes visible.
812 if (method
->klass
== mono_defaults
.string_class
&& (!strcmp (method
->name
, "memset") || strstr (method
->name
, "memcpy")))
816 * This could be in mono_de_ss_update method, but mono_find_next_seq_point_for_native_offset is pretty expensive method,
817 * hence we prefer this check here.
819 if (ss_req
->user_assemblies
) {
820 gboolean found
= FALSE
;
821 for (int k
= 0; ss_req
->user_assemblies
[k
]; k
++)
822 if (ss_req
->user_assemblies
[k
] == m_class_get_image (method
->klass
)->assembly
) {
831 * The ip points to the instruction causing the single step event, which is before
832 * the offset recorded in the seq point map, so find the next seq point after ip.
834 if (!mono_find_next_seq_point_for_native_offset (domain
, method
, (guint8
*)ip
- (guint8
*)ji
->code_start
, &info
, &sp
)) {
835 g_assert_not_reached ();
839 il_offset
= sp
.il_offset
;
841 if (!mono_de_ss_update (ss_req
, ji
, &sp
, tls
, ctx
, method
))
844 /* Start single stepping again from the current sequence point */
847 memset (&args
, 0, sizeof (args
));
848 args
.method
= method
;
851 args
.step_to_catch
= FALSE
;
856 mono_de_ss_start (ss_req
, &args
);
858 if ((ss_req
->filter
& STEP_FILTER_STATIC_CTOR
) &&
859 (method
->flags
& METHOD_ATTRIBUTE_SPECIAL_NAME
) &&
860 !strcmp (method
->name
, ".cctor"))
863 // FIXME: Has to lock earlier
865 reqs
= g_ptr_array_new ();
869 g_ptr_array_add (reqs
, ss_req
->req
);
872 bp_events
= rt_callbacks
.create_breakpoint_events (reqs
, NULL
, ji
, EVENT_KIND_BREAKPOINT
);
874 g_ptr_array_free (reqs
, TRUE
);
876 mono_loader_unlock ();
878 rt_callbacks
.process_breakpoint_events (bp_events
, method
, ctx
, il_offset
);
881 mono_de_ss_req_release (ss_req
);
887 * Return FALSE if single stepping needs to continue.
890 mono_de_ss_update (SingleStepReq
*req
, MonoJitInfo
*ji
, SeqPoint
*sp
, void *tls
, MonoContext
*ctx
, MonoMethod
* method
)
892 MonoDebugMethodInfo
*minfo
;
893 MonoDebugSourceLocation
*loc
= NULL
;
896 if ((req
->filter
& STEP_FILTER_STATIC_CTOR
)) {
897 DbgEngineStackFrame
**frames
;
899 rt_callbacks
.ss_calculate_framecount (tls
, ctx
, TRUE
, &frames
, &nframes
);
901 gboolean ret
= FALSE
;
902 gboolean method_in_stack
= FALSE
;
904 for (int i
= 0; i
< nframes
; i
++) {
905 MonoMethod
*external_method
= frames
[i
]->method
;
906 if (method
== external_method
)
907 method_in_stack
= TRUE
;
910 ret
= (external_method
->flags
& METHOD_ATTRIBUTE_SPECIAL_NAME
);
911 ret
= ret
&& !strcmp (external_method
->name
, ".cctor");
912 ret
= ret
&& (external_method
!= req
->start_method
);
916 if (!method_in_stack
) {
917 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
);
918 /*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);*/
920 for (int i
=0; i
< nframes
; i
++)
921 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
));
924 rt_callbacks
.ss_discard_frame_context (tls
);
930 if (req
->async_stepout_method
== method
) {
931 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
);
935 if (req
->depth
== STEP_DEPTH_OVER
&& (sp
->flags
& MONO_SEQ_POINT_FLAG_NONEMPTY_STACK
) && !(sp
->flags
& MONO_SEQ_POINT_FLAG_NESTED_CALL
)) {
937 * These seq points are inserted by the JIT after calls, step over needs to skip them.
939 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
);
943 if ((req
->depth
== STEP_DEPTH_OVER
|| req
->depth
== STEP_DEPTH_OUT
) && hit
&& !req
->async_stepout_method
) {
944 gboolean is_step_out
= req
->depth
== STEP_DEPTH_OUT
;
946 rt_callbacks
.ss_calculate_framecount (tls
, ctx
, FALSE
, NULL
, &nframes
);
948 // Because functions can call themselves recursively, we need to make sure we're stopping at the right stack depth.
949 // In case of step out, the target is the frame *enclosing* the one where the request was made.
950 int target_frames
= req
->nframes
+ (is_step_out
? -1 : 0);
951 if (req
->nframes
> 0 && nframes
> 0 && nframes
> target_frames
) {
952 /* Hit the breakpoint in a recursive call, don't halt */
953 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");
958 if (req
->depth
== STEP_DEPTH_INTO
&& req
->size
== STEP_SIZE_MIN
&& (sp
->flags
& MONO_SEQ_POINT_FLAG_NONEMPTY_STACK
) && req
->start_method
) {
960 rt_callbacks
.ss_calculate_framecount (tls
, ctx
, FALSE
, NULL
, &nframes
);
961 if (req
->start_method
== method
&& req
->nframes
&& nframes
== req
->nframes
) { //Check also frame count(could be recursion)
962 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
);
967 MonoDebugMethodAsyncInfo
* async_method
= mono_debug_lookup_method_async_debug_info (method
);
969 for (int i
= 0; i
< async_method
->num_awaits
; i
++) {
970 if (async_method
->yield_offsets
[i
] == sp
->il_offset
|| async_method
->resume_offsets
[i
] == sp
->il_offset
) {
971 mono_debug_free_method_async_debug_info (async_method
);
975 mono_debug_free_method_async_debug_info (async_method
);
978 if (req
->size
!= STEP_SIZE_LINE
)
981 /* Have to check whenever a different source line was reached */
982 minfo
= mono_debug_lookup_method (method
);
985 loc
= mono_debug_method_lookup_location (minfo
, sp
->il_offset
);
988 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
);
989 req
->last_method
= method
;
991 } else if (loc
&& method
== req
->last_method
&& loc
->row
== req
->last_line
) {
993 rt_callbacks
.ss_calculate_framecount (tls
, ctx
, FALSE
, NULL
, &nframes
);
994 if (nframes
== req
->nframes
) { // If the frame has changed we're clearly not on the same source line.
995 DEBUG_PRINTF (1, "[%p] Same source line (%d), continuing single stepping.\n", (gpointer
) (gsize
) mono_native_thread_id_get (), loc
->row
);
1001 req
->last_method
= method
;
1002 req
->last_line
= loc
->row
;
1003 mono_debug_free_source_location (loc
);
1010 mono_de_process_breakpoint (void *void_tls
, gboolean from_signal
)
1012 DebuggerTlsData
*tls
= (DebuggerTlsData
*)void_tls
;
1016 guint32 native_offset
;
1017 GPtrArray
*bp_reqs
, *ss_reqs_orig
, *ss_reqs
;
1018 EventKind kind
= EVENT_KIND_BREAKPOINT
;
1019 MonoContext
*ctx
= rt_callbacks
.tls_get_restore_state (tls
);
1021 MonoSeqPointInfo
*info
;
1025 if (rt_callbacks
.try_process_suspend (tls
, ctx
))
1028 ip
= (guint8
*)MONO_CONTEXT_GET_IP (ctx
);
1030 ji
= get_top_method_ji (ip
, NULL
, (gpointer
*)&ip
);
1031 g_assert (ji
&& !ji
->is_trampoline
);
1032 method
= jinfo_get_method (ji
);
1034 /* Compute the native offset of the breakpoint from the ip */
1035 native_offset
= ip
- (guint8
*)ji
->code_start
;
1037 if (!rt_callbacks
.begin_breakpoint_processing (tls
, ctx
, ji
, from_signal
))
1040 if (method
->wrapper_type
)
1043 bp_reqs
= g_ptr_array_new ();
1044 ss_reqs
= g_ptr_array_new ();
1045 ss_reqs_orig
= g_ptr_array_new ();
1047 mono_loader_lock ();
1050 * The ip points to the instruction causing the breakpoint event, which is after
1051 * the offset recorded in the seq point map, so find the prev seq point before ip.
1053 found_sp
= mono_find_prev_seq_point_for_native_offset (mono_domain_get (), method
, native_offset
, &info
, &sp
);
1056 no_seq_points_found (method
, native_offset
);
1058 g_assert (found_sp
);
1060 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
);
1062 mono_debugger_log_bp_hit (tls
, method
, sp
.il_offset
);
1064 mono_de_collect_breakpoints_by_sp (&sp
, ji
, ss_reqs_orig
, bp_reqs
);
1066 if (bp_reqs
->len
== 0 && ss_reqs_orig
->len
== 0) {
1067 /* Maybe a method entry/exit event */
1068 if (sp
.il_offset
== METHOD_ENTRY_IL_OFFSET
)
1069 kind
= EVENT_KIND_METHOD_ENTRY
;
1070 else if (sp
.il_offset
== METHOD_EXIT_IL_OFFSET
)
1071 kind
= EVENT_KIND_METHOD_EXIT
;
1074 /* Process single step requests */
1075 for (i
= 0; i
< ss_reqs_orig
->len
; ++i
) {
1076 EventRequest
*req
= (EventRequest
*)g_ptr_array_index (ss_reqs_orig
, i
);
1077 SingleStepReq
*ss_req
= (SingleStepReq
*)req
->info
;
1080 //if we hit async_stepout_method, it's our no matter which thread
1081 if ((ss_req
->async_stepout_method
!= method
) && (ss_req
->async_id
|| mono_thread_internal_current () != ss_req
->thread
)) {
1082 DbgEngineStackFrame
**frames
;
1084 //We have different thread and we don't have async stepping in progress
1085 //it's breakpoint in parallel thread, ignore it
1086 if (ss_req
->async_id
== 0)
1089 rt_callbacks
.ss_discard_frame_context (tls
);
1090 rt_callbacks
.ss_calculate_framecount (tls
, ctx
, FALSE
, &frames
, &nframes
);
1091 //make sure we have enough data to get current async method instance id
1092 if (nframes
== 0 || !rt_callbacks
.ensure_jit (frames
[0]))
1095 //Check method is async before calling get_this_async_id
1096 MonoDebugMethodAsyncInfo
* asyncMethod
= mono_debug_lookup_method_async_debug_info (method
);
1100 mono_debug_free_method_async_debug_info (asyncMethod
);
1102 //breakpoint was hit in parallelly executing async method, ignore it
1103 if (ss_req
->async_id
!= rt_callbacks
.get_this_async_id (frames
[0]))
1107 //Update stepping request to new thread/frame_count that we are continuing on
1108 //so continuing with normal stepping works as expected
1109 if (ss_req
->async_stepout_method
|| ss_req
->async_id
) {
1111 rt_callbacks
.ss_discard_frame_context (tls
);
1112 rt_callbacks
.ss_calculate_framecount (tls
, ctx
, FALSE
, NULL
, &nframes
);
1113 ss_req
->thread
= mono_thread_internal_current ();
1114 ss_req
->nframes
= nframes
;
1117 hit
= mono_de_ss_update (ss_req
, ji
, &sp
, tls
, ctx
, method
);
1119 g_ptr_array_add (ss_reqs
, req
);
1121 SingleStepArgs args
;
1122 memset (&args
, 0, sizeof (args
));
1123 args
.method
= method
;
1126 args
.step_to_catch
= FALSE
;
1131 mono_de_ss_start (ss_req
, &args
);
1134 void *bp_events
= rt_callbacks
.create_breakpoint_events (ss_reqs
, bp_reqs
, ji
, kind
);
1136 mono_loader_unlock ();
1138 g_ptr_array_free (bp_reqs
, TRUE
);
1139 g_ptr_array_free (ss_reqs
, TRUE
);
1141 rt_callbacks
.process_breakpoint_events (bp_events
, method
, ctx
, 0);
1147 * Reject breakpoint if it is a duplicate of one already in list or hash table.
1150 ss_bp_is_unique (GSList
*bps
, GHashTable
*ss_req_bp_cache
, MonoMethod
*method
, guint32 il_offset
)
1152 if (ss_req_bp_cache
) {
1153 MonoBreakpoint dummy
= {method
, (long)il_offset
, NULL
, NULL
};
1154 return !g_hash_table_lookup (ss_req_bp_cache
, &dummy
);
1156 for (GSList
*l
= bps
; l
; l
= l
->next
) {
1157 MonoBreakpoint
*bp
= (MonoBreakpoint
*)l
->data
;
1158 if (bp
->method
== method
&& bp
->il_offset
== il_offset
)
1167 * GHashTable equality for a MonoBreakpoint (only care about method and il_offset fields)
1170 ss_bp_eq (gconstpointer ka
, gconstpointer kb
)
1172 const MonoBreakpoint
*s1
= (const MonoBreakpoint
*)ka
;
1173 const MonoBreakpoint
*s2
= (const MonoBreakpoint
*)kb
;
1174 return (s1
->method
== s2
->method
&& s1
->il_offset
== s2
->il_offset
) ? 1 : 0;
1180 * GHashTable hash for a MonoBreakpoint (only care about method and il_offset fields)
1183 ss_bp_hash (gconstpointer data
)
1185 const MonoBreakpoint
*s
= (const MonoBreakpoint
*)data
;
1186 guint hash
= (guint
) (uintptr_t) s
->method
;
1187 hash
^= ((guint
)s
->il_offset
) << 16; // Assume low bits are more interesting
1188 hash
^= ((guint
)s
->il_offset
) >> 16;
1192 #define MAX_LINEAR_SCAN_BPS 7
1197 * Create a new breakpoint and add it to a step request.
1198 * Will adjust the bp count and cache used by mono_de_ss_start.
1201 ss_bp_add_one (SingleStepReq
*ss_req
, int *ss_req_bp_count
, GHashTable
**ss_req_bp_cache
,
1202 MonoMethod
*method
, guint32 il_offset
)
1204 // This list is getting too long, switch to using the hash table
1205 if (!*ss_req_bp_cache
&& *ss_req_bp_count
> MAX_LINEAR_SCAN_BPS
) {
1206 *ss_req_bp_cache
= g_hash_table_new (ss_bp_hash
, ss_bp_eq
);
1207 for (GSList
*l
= ss_req
->bps
; l
; l
= l
->next
)
1208 g_hash_table_insert (*ss_req_bp_cache
, l
->data
, l
->data
);
1211 if (ss_bp_is_unique (ss_req
->bps
, *ss_req_bp_cache
, method
, il_offset
)) {
1212 // Create and add breakpoint
1213 MonoBreakpoint
*bp
= mono_de_set_breakpoint (method
, il_offset
, ss_req
->req
, NULL
);
1214 ss_req
->bps
= g_slist_append (ss_req
->bps
, bp
);
1215 if (*ss_req_bp_cache
)
1216 g_hash_table_insert (*ss_req_bp_cache
, bp
, bp
);
1217 (*ss_req_bp_count
)++;
1219 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 is_last_non_empty (SeqPoint
* sp
, MonoSeqPointInfo
*info
)
1228 SeqPoint
* next
= g_new (SeqPoint
, sp
->next_len
);
1229 mono_seq_point_init_next (info
, *sp
, next
);
1230 for (int i
= 0; i
< sp
->next_len
; i
++) {
1231 if (next
[i
].flags
& MONO_SEQ_POINT_FLAG_NONEMPTY_STACK
&& !(next
[i
].flags
& MONO_SEQ_POINT_FLAG_NESTED_CALL
)) {
1232 if (!is_last_non_empty (&next
[i
], info
)) {
1248 * Start the single stepping operation given by SS_REQ from the sequence point SP.
1249 * If CTX is not set, then this can target any thread. If CTX is set, then TLS should
1250 * belong to the same thread as CTX.
1251 * If FRAMES is not-null, use that instead of tls->frames for placing breakpoints etc.
1254 mono_de_ss_start (SingleStepReq
*ss_req
, SingleStepArgs
*ss_args
)
1256 int i
, j
, frame_index
;
1257 SeqPoint
*next_sp
, *parent_sp
= NULL
;
1258 SeqPoint local_sp
, local_parent_sp
;
1260 MonoSeqPointInfo
*parent_info
;
1261 MonoMethod
*parent_sp_method
= NULL
;
1262 gboolean enable_global
= FALSE
;
1264 // When 8 or more entries are in bps, we build a hash table to serve as a set of breakpoints.
1265 // Recreating this on each pass is a little wasteful but at least keeps behavior linear.
1266 int ss_req_bp_count
= g_slist_length (ss_req
->bps
);
1267 GHashTable
*ss_req_bp_cache
= NULL
;
1269 /* Stop the previous operation */
1272 gboolean locked
= FALSE
;
1274 void *tls
= ss_args
->tls
;
1275 MonoMethod
*method
= ss_args
->method
;
1276 DbgEngineStackFrame
**frames
= ss_args
->frames
;
1277 int nframes
= ss_args
->nframes
;
1278 SeqPoint
*sp
= &ss_args
->sp
;
1280 /* this can happen on a single step in a exception on android (Mono_UnhandledException_internal) and on IOS */
1285 * Implement single stepping using breakpoints if possible.
1287 if (ss_args
->step_to_catch
) {
1288 ss_bp_add_one (ss_req
, &ss_req_bp_count
, &ss_req_bp_cache
, method
, sp
->il_offset
);
1292 if (ss_args
->ctx
&& !frames
) {
1294 mono_loader_lock ();
1297 /* Need parent frames */
1298 rt_callbacks
.ss_calculate_framecount (tls
, ss_args
->ctx
, FALSE
, &frames
, &nframes
);
1301 MonoDebugMethodAsyncInfo
* asyncMethod
= mono_debug_lookup_method_async_debug_info (method
);
1303 /* Need to stop in catch clauses as well */
1304 for (i
= ss_req
->depth
== STEP_DEPTH_OUT
? 1 : 0; i
< nframes
; ++i
) {
1305 DbgEngineStackFrame
*frame
= frames
[i
];
1308 MonoJitInfo
*jinfo
= frame
->ji
;
1309 for (j
= 0; j
< jinfo
->num_clauses
; ++j
) {
1310 // In case of async method we don't want to place breakpoint on last catch handler(which state machine added for whole method)
1311 if (asyncMethod
&& asyncMethod
->num_awaits
&& i
== 0 && j
+ 1 == jinfo
->num_clauses
)
1313 MonoJitExceptionInfo
*ei
= &jinfo
->clauses
[j
];
1315 if (mono_find_next_seq_point_for_native_offset (frame
->domain
, frame
->method
, (char*)ei
->handler_start
- (char*)jinfo
->code_start
, NULL
, &local_sp
))
1316 ss_bp_add_one (ss_req
, &ss_req_bp_count
, &ss_req_bp_cache
, frame
->method
, local_sp
.il_offset
);
1321 if (asyncMethod
&& asyncMethod
->num_awaits
&& nframes
&& rt_callbacks
.ensure_jit (frames
[0])) {
1322 //asyncMethod has value and num_awaits > 0, this means we are inside async method with awaits
1324 // Check if we hit yield_offset during normal stepping, because if we did...
1325 // Go into special async stepping mode which places breakpoint on resumeOffset
1326 // of this await call and sets async_id so we can distinguish it from parallel executions
1327 for (i
= 0; i
< asyncMethod
->num_awaits
; i
++) {
1328 if (sp
->il_offset
== asyncMethod
->yield_offsets
[i
]) {
1329 ss_req
->async_id
= rt_callbacks
.get_this_async_id (frames
[0]);
1330 ss_bp_add_one (ss_req
, &ss_req_bp_count
, &ss_req_bp_cache
, method
, asyncMethod
->resume_offsets
[i
]);
1331 g_hash_table_destroy (ss_req_bp_cache
);
1332 mono_debug_free_method_async_debug_info (asyncMethod
);
1334 mono_loader_unlock ();
1338 //If we are at end of async method and doing step-in or step-over...
1339 //Switch to step-out, so whole NotifyDebuggerOfWaitCompletion magic happens...
1340 if (is_last_non_empty (sp
, ss_args
->info
)) {
1341 ss_req
->depth
= STEP_DEPTH_OUT
;//setting depth to step-out is important, don't inline IF, because code later depends on this
1343 if (ss_req
->depth
== STEP_DEPTH_OUT
) {
1344 //If we are inside `async void` method, do normal step-out
1345 if (rt_callbacks
.set_set_notification_for_wait_completion_flag (frames
[0])) {
1346 ss_req
->async_id
= rt_callbacks
.get_this_async_id (frames
[0]);
1347 ss_req
->async_stepout_method
= rt_callbacks
.get_notify_debugger_of_wait_completion_method ();
1348 ss_bp_add_one (ss_req
, &ss_req_bp_count
, &ss_req_bp_cache
, ss_req
->async_stepout_method
, 0);
1349 g_hash_table_destroy (ss_req_bp_cache
);
1350 mono_debug_free_method_async_debug_info (asyncMethod
);
1352 mono_loader_unlock ();
1359 mono_debug_free_method_async_debug_info (asyncMethod
);
1362 * Find the first sequence point in the current or in a previous frame which
1363 * is not the last in its method.
1365 if (ss_req
->depth
== STEP_DEPTH_OUT
) {
1366 /* Ignore seq points in current method */
1367 while (frame_index
< nframes
) {
1368 DbgEngineStackFrame
*frame
= frames
[frame_index
];
1370 method
= frame
->method
;
1371 found_sp
= mono_find_prev_seq_point_for_native_offset (frame
->domain
, frame
->method
, frame
->native_offset
, &ss_args
->info
, &local_sp
);
1372 sp
= (found_sp
)? &local_sp
: NULL
;
1374 if (sp
&& sp
->next_len
!= 0)
1377 // There could be method calls before the next seq point in the caller when using nested calls
1378 //enable_global = TRUE;
1380 if (sp
&& sp
->next_len
== 0) {
1382 while (frame_index
< nframes
) {
1383 DbgEngineStackFrame
*frame
= frames
[frame_index
];
1385 method
= frame
->method
;
1386 found_sp
= mono_find_prev_seq_point_for_native_offset (frame
->domain
, frame
->method
, frame
->native_offset
, &ss_args
->info
, &local_sp
);
1387 sp
= (found_sp
)? &local_sp
: NULL
;
1388 if (sp
&& sp
->next_len
!= 0)
1394 /* Have to put a breakpoint into a parent frame since the seq points might not cover all control flow out of the method */
1395 while (frame_index
< nframes
) {
1396 DbgEngineStackFrame
*frame
= frames
[frame_index
];
1398 parent_sp_method
= frame
->method
;
1399 found_sp
= mono_find_prev_seq_point_for_native_offset (frame
->domain
, frame
->method
, frame
->native_offset
, &parent_info
, &local_parent_sp
);
1400 parent_sp
= found_sp
? &local_parent_sp
: NULL
;
1401 if (found_sp
&& parent_sp
->next_len
!= 0)
1409 if (sp
&& sp
->next_len
> 0) {
1410 SeqPoint
* next
= g_new(SeqPoint
, sp
->next_len
);
1412 mono_seq_point_init_next (ss_args
->info
, *sp
, next
);
1413 for (i
= 0; i
< sp
->next_len
; i
++) {
1416 ss_bp_add_one (ss_req
, &ss_req_bp_count
, &ss_req_bp_cache
, method
, next_sp
->il_offset
);
1422 SeqPoint
* next
= g_new(SeqPoint
, parent_sp
->next_len
);
1424 mono_seq_point_init_next (parent_info
, *parent_sp
, next
);
1425 for (i
= 0; i
< parent_sp
->next_len
; i
++) {
1428 ss_bp_add_one (ss_req
, &ss_req_bp_count
, &ss_req_bp_cache
, parent_sp_method
, next_sp
->il_offset
);
1433 if (ss_req
->nframes
== 0)
1434 ss_req
->nframes
= nframes
;
1436 if ((ss_req
->depth
== STEP_DEPTH_OVER
) && (!sp
&& !parent_sp
)) {
1437 DEBUG_PRINTF (1, "[dbg] No parent frame for step over, transition to step into.\n");
1439 * This is needed since if we leave managed code, and later return to it, step over
1440 * is not going to stop.
1441 * This approach is a bit ugly, since we change the step depth, but it only affects
1442 * clients who reuse the same step request, and only in this special case.
1444 ss_req
->depth
= STEP_DEPTH_INTO
;
1447 if (ss_req
->depth
== STEP_DEPTH_INTO
) {
1448 /* Enable global stepping so we stop at method entry too */
1449 enable_global
= TRUE
;
1453 * The ctx/frame info computed above will become invalid when we continue.
1455 rt_callbacks
.ss_discard_frame_context (tls
);
1458 if (enable_global
) {
1459 DEBUG_PRINTF (1, "[dbg] Turning on global single stepping.\n");
1460 ss_req
->global
= TRUE
;
1461 mono_de_start_single_stepping ();
1462 } else if (!ss_req
->bps
) {
1463 DEBUG_PRINTF (1, "[dbg] Turning on global single stepping.\n");
1464 ss_req
->global
= TRUE
;
1465 mono_de_start_single_stepping ();
1467 ss_req
->global
= FALSE
;
1470 g_hash_table_destroy (ss_req_bp_cache
);
1473 mono_loader_unlock ();
1476 rt_callbacks
.ss_args_destroy (ss_args
);
1481 * Start single stepping of thread THREAD
1484 mono_de_ss_create (MonoInternalThread
*thread
, StepSize size
, StepDepth depth
, StepFilter filter
, EventRequest
*req
)
1486 int err
= rt_callbacks
.ensure_runtime_is_suspended ();
1490 // FIXME: Multiple requests
1492 DEBUG_PRINTF (0, "Received a single step request while the previous one was still active.\n");
1493 return DE_ERR_NOT_IMPLEMENTED
;
1496 DEBUG_PRINTF (1, "[dbg] Starting single step of thread %p (depth=%s).\n", thread
, ss_depth_to_string (depth
));
1498 SingleStepReq
*ss_req
= g_new0 (SingleStepReq
, 1);
1500 ss_req
->thread
= thread
;
1501 ss_req
->size
= size
;
1502 ss_req
->depth
= depth
;
1503 ss_req
->filter
= filter
;
1504 ss_req
->refcount
= 1;
1507 for (int i
= 0; i
< req
->nmodifiers
; i
++) {
1508 if (req
->modifiers
[i
].kind
== MOD_KIND_ASSEMBLY_ONLY
) {
1509 ss_req
->user_assemblies
= req
->modifiers
[i
].data
.assemblies
;
1514 SingleStepArgs args
;
1515 err
= rt_callbacks
.ss_create_init_args (ss_req
, &args
);
1519 the_ss_req
= ss_req
;
1521 mono_de_ss_start (ss_req
, &args
);
1527 * mono_de_set_log_level:
1529 * Configures logging level and output file. Must be called together with mono_de_init.
1532 mono_de_set_log_level (int level
, FILE *file
)
1541 * Inits the shared debugger engine. Not reentrant.
1544 mono_de_init (DebuggerEngineCallbacks
*cbs
)
1546 rt_callbacks
= *cbs
;
1547 mono_coop_mutex_init_recursive (&debug_mutex
);
1550 breakpoints_init ();
1552 mono_debugger_log_init ();
1556 mono_de_cleanup (void)
1558 breakpoints_cleanup ();
1563 mono_debugger_free_objref (gpointer value
)
1565 ObjRef
*o
= (ObjRef
*)value
;
1567 mono_gchandle_free_internal (o
->handle
);