/*
- * debugger-agent.c: Debugger back-end module
+ * debugger-agent.c: Soft Debugger back-end module
*
* Author:
* Zoltan Varga (vargaz@gmail.com)
*
- * (C) 2009 Novell, Inc.
+ * Copyright 2009-2010 Novell, Inc.
*/
#include <config.h>
#include <mono/metadata/gc-internal.h>
#include <mono/metadata/threads-types.h>
#include <mono/metadata/socket-io.h>
+#include <mono/metadata/assembly.h>
#include <mono/utils/mono-semaphore.h>
+#include <mono/utils/mono-error-internals.h>
#include "debugger-agent.h"
#include "mini.h"
#endif
#ifndef DISABLE_DEBUGGER_AGENT
+
#include <mono/io-layer/mono-mutex.h>
/* Definitions to make backporting to 2.6 easier */
GSList *onthrow;
int timeout;
char *launch;
+ gboolean embedding;
+ gboolean defer;
} AgentConfig;
typedef struct
{
int id;
- guint32 il_offset;
+ guint32 il_offset, native_offset;
MonoDomain *domain;
MonoMethod *method;
+ /*
+ * If method is gshared, this is the actual instance, otherwise this is equal to
+ * method.
+ */
+ MonoMethod *actual_method;
MonoContext ctx;
MonoDebugMethodJitInfo *jit;
int flags;
gboolean has_async_ctx;
+ gboolean has_filter_ctx;
+ MonoContext filter_ctx;
+ MonoLMF *filter_lmf;
+
/*
* The lmf where the stack walk can be started for running threads.
*/
* The current mono_runtime_invoke invocation.
*/
InvokeData *invoke;
+
+ /*
+ * The context where single stepping should resume while the thread is suspended because
+ * of an EXCEPTION event.
+ */
+ MonoContext catch_ctx;
+
+ gboolean has_catch_ctx;
} DebuggerTlsData;
/*
#define HEADER_LENGTH 11
#define MAJOR_VERSION 2
-#define MINOR_VERSION 1
+#define MINOR_VERSION 3
typedef enum {
CMD_SET_VM = 1,
ERR_INVALID_ARGUMENT = 102,
ERR_UNLOADED = 103,
ERR_NO_INVOCATION = 104,
- ERR_ABSENT_INFORMATION = 105
+ ERR_ABSENT_INFORMATION = 105,
+ ERR_NO_SEQ_POINT_AT_IL_OFFSET = 106
} ErrorCode;
typedef enum {
CMD_THREAD_GET_NAME = 2,
CMD_THREAD_GET_STATE = 3,
CMD_THREAD_GET_INFO = 4,
- CMD_THREAD_GET_ID = 5
+ CMD_THREAD_GET_ID = 5,
+ CMD_THREAD_GET_TID = 6
} CmdThread;
typedef enum {
CMD_TYPE_GET_FIELD_CATTRS = 11,
CMD_TYPE_GET_PROPERTY_CATTRS = 12,
CMD_TYPE_GET_SOURCE_FILES_2 = 13,
+ CMD_TYPE_GET_VALUES_2 = 14
} CmdType;
typedef enum {
static gint32 inited;
static int conn_fd;
+static int listen_fd;
static int packet_id = 0;
static int log_level;
-static FILE *log_file;
+static gboolean embedding;
-/* Classes whose class load event has been sent */
-static GHashTable *loaded_classes;
+static FILE *log_file;
/* Assemblies whose assembly load event has no been sent yet */
static GPtrArray *pending_assembly_loads;
-/* Types whose type load event has no been sent yet */
-static GPtrArray *pending_type_loads;
-
/* Whenever the debugger thread has exited */
static gboolean debugger_thread_exited;
static void runtime_shutdown (MonoProfiler *prof);
-static void thread_startup (MonoProfiler *prof, intptr_t tid);
+static void thread_startup (MonoProfiler *prof, uintptr_t tid);
-static void thread_end (MonoProfiler *prof, intptr_t tid);
+static void thread_end (MonoProfiler *prof, uintptr_t tid);
static void appdomain_load (MonoProfiler *prof, MonoDomain *domain, int result);
static void appdomain_unload (MonoProfiler *prof, MonoDomain *domain);
+static void emit_appdomain_load (gpointer key, gpointer value, gpointer user_data);
+
+static void emit_thread_start (gpointer key, gpointer value, gpointer user_data);
+
static void invalidate_each_thread (gpointer key, gpointer value, gpointer user_data);
static void assembly_load (MonoProfiler *prof, MonoAssembly *assembly, int result);
static void assembly_unload (MonoProfiler *prof, MonoAssembly *assembly);
+static void emit_assembly_load (gpointer assembly, gpointer user_data);
+
+static void emit_type_load (gpointer key, gpointer type, gpointer user_data);
+
static void start_runtime_invoke (MonoProfiler *prof, MonoMethod *method);
static void end_runtime_invoke (MonoProfiler *prof, MonoMethod *method);
static void clear_event_requests_for_assembly (MonoAssembly *assembly);
+static void clear_types_for_assembly (MonoAssembly *assembly);
+
+static void clear_breakpoints_for_domain (MonoDomain *domain);
+
+static void process_profiler_event (EventKind event, gpointer arg);
+
/* Submodule init/cleanup */
static void breakpoints_init (void);
static void breakpoints_cleanup (void);
static void suspend_init (void);
-static void ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint *sp, MonoSeqPointInfo *info, MonoContext *ctx, DebuggerTlsData *tls);
+static void ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint *sp, MonoSeqPointInfo *info, MonoContext *ctx, DebuggerTlsData *tls, gboolean step_to_catch);
static ErrorCode ss_create (MonoInternalThread *thread, StepSize size, StepDepth depth, EventRequest *req);
static void ss_destroy (SingleStepReq *req);
static void start_debugger_thread (void);
+static void stop_debugger_thread (void);
static void finish_agent_init (gboolean on_startup);
fprintf (stderr, " address=<hostname>:<port>\tAddress to connect to (mandatory)\n");
fprintf (stderr, " loglevel=<n>\t\t\tLog level (defaults to 0)\n");
fprintf (stderr, " logfile=<file>\t\tFile to log to (defaults to stdout)\n");
- fprintf (stderr, " suspend=y/n\t\t\tWhenever to suspend after startup.\n");
+ fprintf (stderr, " suspend=y/n\t\t\tWhether to suspend after startup.\n");
fprintf (stderr, " timeout=<n>\t\t\tTimeout for connecting in milliseconds.\n");
+ fprintf (stderr, " server=y/n\t\t\tWhether to listen for a client connection.\n");
fprintf (stderr, " help\t\t\t\tPrint this help.\n");
}
agent_config.enabled = TRUE;
agent_config.suspend = TRUE;
agent_config.server = FALSE;
+ agent_config.defer = FALSE;
+ agent_config.address = NULL;
args = g_strsplit (options, ",", -1);
for (ptr = args; ptr && *ptr; ptr ++) {
agent_config.timeout = atoi (arg + 8);
} else if (strncmp (arg, "launch=", 7) == 0) {
agent_config.launch = g_strdup (arg + 7);
+ } else if (strncmp (arg, "embedding=", 10) == 0) {
+ agent_config.embedding = atoi (arg + 10) == 1;
} else {
print_usage ();
exit (1);
}
}
+ if (agent_config.server && !agent_config.suspend) {
+ /* Waiting for deferred attachment */
+ agent_config.defer = TRUE;
+ if (agent_config.address == NULL) {
+ agent_config.address = g_strdup_printf ("0.0.0.0:%u", 56000 + (GetCurrentProcessId () % 1000));
+ }
+ }
+
if (agent_config.transport == NULL) {
fprintf (stderr, "debugger-agent: The 'transport' option is mandatory.\n");
exit (1);
debugger_tls_id = TlsAlloc ();
thread_to_tls = mono_g_hash_table_new_type (NULL, NULL, MONO_HASH_KEY_GC);
- MONO_GC_REGISTER_ROOT (thread_to_tls);
+ MONO_GC_REGISTER_ROOT_FIXED (thread_to_tls);
tid_to_thread = mono_g_hash_table_new_type (NULL, NULL, MONO_HASH_VALUE_GC);
- MONO_GC_REGISTER_ROOT (tid_to_thread);
+ MONO_GC_REGISTER_ROOT_FIXED (tid_to_thread);
tid_to_thread_obj = mono_g_hash_table_new_type (NULL, NULL, MONO_HASH_VALUE_GC);
- MONO_GC_REGISTER_ROOT (tid_to_thread_obj);
+ MONO_GC_REGISTER_ROOT_FIXED (tid_to_thread_obj);
- loaded_classes = g_hash_table_new (mono_aligned_addr_hash, NULL);
pending_assembly_loads = g_ptr_array_new ();
- pending_type_loads = g_ptr_array_new ();
domains = g_hash_table_new (mono_aligned_addr_hash, NULL);
log_level = agent_config.log_level;
+ embedding = agent_config.embedding;
+ disconnected = TRUE;
+
if (agent_config.log_file) {
log_file = fopen (agent_config.log_file, "w+");
if (!log_file) {
if (!inited)
return;
- /* This will interrupt the agent thread */
- /* Close the read part only so it can still send back replies */
-#ifdef HOST_WIN32
- shutdown (conn_fd, SD_RECEIVE);
-#else
- shutdown (conn_fd, SHUT_RD);
-#endif
-
- /*
- * Wait for the thread to exit.
- *
- * If we continue with the shutdown without waiting for it, then the client might
- * not receive an answer to its last command like a resume.
- * The WaitForSingleObject infrastructure doesn't seem to work during shutdown, so
- * use pthreads.
- */
- //WaitForSingleObject (debugger_thread_handle, INFINITE);
- if (GetCurrentThreadId () != debugger_thread_id) {
- mono_mutex_lock (&debugger_thread_exited_mutex);
- if (!debugger_thread_exited) {
-#ifdef HOST_WIN32
- if (WAIT_TIMEOUT == WaitForSingleObject(debugger_thread_exited_cond, 0)) {
- mono_mutex_unlock (&debugger_thread_exited_mutex);
- Sleep(0);
- mono_mutex_lock (&debugger_thread_exited_mutex);
- }
-#else
- mono_cond_wait (&debugger_thread_exited_cond, &debugger_thread_exited_mutex);
-#endif
- }
- mono_mutex_unlock (&debugger_thread_exited_mutex);
- }
+ stop_debugger_thread ();
breakpoints_cleanup ();
objrefs_cleanup ();
ids_cleanup ();
-
-#ifdef HOST_WIN32
- shutdown (conn_fd, SD_BOTH);
-#else
- shutdown (conn_fd, SHUT_RDWR);
-#endif
mono_mutex_destroy (&debugger_thread_exited_mutex);
mono_cond_destroy (&debugger_thread_exited_cond);
} while ((res > 0 && total < len) || (res == -1 && errno == EINTR));
return total;
}
+
+#ifndef TARGET_PS3
+#define HAVE_GETADDRINFO 1
+#endif
+
+static int
+transport_accept (int socket_fd)
+{
+ conn_fd = accept (socket_fd, NULL, NULL);
+ if (conn_fd == -1) {
+ fprintf (stderr, "debugger-agent: Unable to listen on %d\n", socket_fd);
+ } else {
+ DEBUG (1, fprintf (log_file, "Accepted connection from client, connection fd=%d.\n", conn_fd));
+ }
+
+ return conn_fd;
+}
+
+static gboolean
+transport_handshake (void)
+{
+ char handshake_msg [128];
+ guint8 buf [128];
+ int res;
+
+ /* Write handshake message */
+ sprintf (handshake_msg, "DWP-Handshake");
+ do {
+ res = send (conn_fd, handshake_msg, strlen (handshake_msg), 0);
+ } while (res == -1 && errno == EINTR);
+ g_assert (res != -1);
+
+ /* Read answer */
+ res = recv_length (conn_fd, buf, strlen (handshake_msg), 0);
+ if ((res != strlen (handshake_msg)) || (memcmp (buf, handshake_msg, strlen (handshake_msg) != 0))) {
+ fprintf (stderr, "debugger-agent: DWP handshake failed.\n");
+ return FALSE;
+ }
+
+ /*
+ * To support older clients, the client sends its protocol version after connecting
+ * using a command. Until that is received, default to our protocol version.
+ */
+ major_version = MAJOR_VERSION;
+ minor_version = MINOR_VERSION;
+ protocol_version_set = FALSE;
+
+ /*
+ * Set TCP_NODELAY on the socket so the client receives events/command
+ * results immediately.
+ */
+ {
+ int flag = 1;
+ int result = setsockopt (conn_fd,
+ IPPROTO_TCP,
+ TCP_NODELAY,
+ (char *) &flag,
+ sizeof(int));
+ g_assert (result >= 0);
+ }
+
+ return TRUE;
+}
+
/*
* transport_connect:
*
static void
transport_connect (const char *host, int port)
{
+#ifdef HAVE_GETADDRINFO
struct addrinfo hints;
struct addrinfo *result, *rp;
+#else
+ struct hostent *result;
+#endif
int sfd, s, res;
char port_string [128];
- char handshake_msg [128];
- guint8 buf [128];
conn_fd = -1;
+ listen_fd = -1;
if (host) {
sprintf (port_string, "%d", port);
mono_network_init ();
/* Obtain address(es) matching host/port */
-
+#ifdef HAVE_GETADDRINFO
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
s = getaddrinfo (host, port_string, &hints, &result);
if (s != 0) {
- fprintf (stderr, "debugger-agent: Unable to connect to %s:%d: %s\n", host, port, gai_strerror (s));
+ fprintf (stderr, "debugger-agent: Unable to resolve %s:%d: %s\n", host, port, gai_strerror (s));
exit (1);
}
+#else
+ /* The PS3 doesn't even have _r or hstrerror () */
+ result = gethostbyname (host);
+ if (!result) {
+ fprintf (stderr, "debugger-agent: Unable to resolve %s:%d: %d\n", host, port, h_errno);
+ }
+#endif
}
if (agent_config.server) {
+#ifdef HAVE_GETADDRINFO
/* Wait for a connection */
if (!host) {
struct sockaddr_in addr;
fprintf (stderr, "debugger-agent: Unable to setup listening socket: %s\n", strerror (errno));
exit (1);
}
+ listen_fd = sfd;
addrlen = sizeof (addr);
memset (&addr, 0, sizeof (addr));
res = listen (sfd, 16);
if (res == -1)
continue;
+ listen_fd = sfd;
break;
}
* http://msdn.microsoft.com/en-us/library/ms737931(VS.85).aspx
* only works with MSVC.
*/
+#ifdef HAVE_GETADDRINFO
freeaddrinfo (result);
+#endif
#endif
}
DEBUG (1, fprintf (log_file, "Listening on %s:%d (timeout=%d ms)...\n", host, port, agent_config.timeout));
+ if (agent_config.defer)
+ return;
+
if (agent_config.timeout) {
fd_set readfds;
struct timeval tv;
res = select (sfd + 1, &readfds, NULL, NULL, &tv);
if (res == 0) {
fprintf (stderr, "debugger-agent: Timed out waiting to connect.\n");
- exit (1);
+ if (!agent_config.defer)
+ exit (1);
}
}
- conn_fd = accept (sfd, NULL, NULL);
- if (conn_fd == -1) {
- fprintf (stderr, "debugger-agent: Unable to listen on %s:%d\n", host, port);
+ conn_fd = transport_accept (sfd);
+ if (conn_fd == -1)
exit (1);
- }
DEBUG (1, fprintf (log_file, "Accepted connection from client, socket fd=%d.\n", conn_fd));
+#else
+ NOT_IMPLEMENTED;
+#endif /* HAVE_GETADDRINFO */
} else {
/* Connect to the specified address */
+#ifdef HAVE_GETADDRINFO
/* FIXME: Respect the timeout */
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket (rp->ai_family, rp->ai_socktype,
close (sfd);
}
+ if (rp == 0) {
+ fprintf (stderr, "debugger-agent: Unable to connect to %s:%d\n", host, port);
+ exit (1);
+ }
+#else
+ sfd = socket (result->h_addrtype, SOCK_STREAM, 0);
+ if (sfd == -1)
+ g_assert_not_reached ();
+ res = connect (sfd, (void*)result->h_addr_list [0], result->h_length);
+ if (res == -1)
+ g_assert_not_reached ();
+#endif
+
conn_fd = sfd;
#ifndef HOST_WIN32
/* See the comment above */
+#ifdef HAVE_GETADDRINFO
freeaddrinfo (result);
#endif
-
- if (rp == 0) {
- fprintf (stderr, "debugger-agent: Unable to connect to %s:%d\n", host, port);
- exit (1);
- }
+#endif
}
- /* Write handshake message */
- sprintf (handshake_msg, "DWP-Handshake");
- do {
- res = send (conn_fd, handshake_msg, strlen (handshake_msg), 0);
- } while (res == -1 && errno == EINTR);
- g_assert (res != -1);
-
- /* Read answer */
- res = recv_length (conn_fd, buf, strlen (handshake_msg), 0);
- if ((res != strlen (handshake_msg)) || (memcmp (buf, handshake_msg, strlen (handshake_msg) != 0))) {
- fprintf (stderr, "debugger-agent: DWP handshake failed.\n");
+ disconnected = !transport_handshake ();
+ if (disconnected)
exit (1);
- }
-
- /*
- * To support older clients, the client sends its protocol version after connecting
- * using a command. Until that is received, default to our protocol version.
- */
- major_version = MAJOR_VERSION;
- minor_version = MINOR_VERSION;
- protocol_version_set = FALSE;
-
- /*
- * Set TCP_NODELAY on the socket so the client receives events/command
- * results immediately.
- */
- {
- int flag = 1;
- int result = setsockopt(conn_fd,
- IPPROTO_TCP,
- TCP_NODELAY,
- (char *) &flag,
- sizeof(int));
- g_assert (result >= 0);
- }
}
static gboolean
return TRUE;
}
+static void
+stop_debugger_thread (void)
+{
+ if (!inited)
+ return;
+
+ /* This will interrupt the agent thread */
+ /* Close the read part only so it can still send back replies */
+ /* Also shut down the connection listener so that we can exit normally */
+#ifdef HOST_WIN32
+ /* SD_RECEIVE doesn't break the recv in the debugger thread */
+ shutdown (conn_fd, SD_BOTH);
+ shutdown (listen_fd, SD_BOTH);
+ closesocket (listen_fd);
+#else
+ shutdown (conn_fd, SHUT_RD);
+ shutdown (listen_fd, SHUT_RDWR);
+ close (listen_fd);
+#endif
+
+ /*
+ * Wait for the thread to exit.
+ *
+ * If we continue with the shutdown without waiting for it, then the client might
+ * not receive an answer to its last command like a resume.
+ * The WaitForSingleObject infrastructure doesn't seem to work during shutdown, so
+ * use pthreads.
+ */
+ //WaitForSingleObject (debugger_thread_handle, INFINITE);
+ if (GetCurrentThreadId () != debugger_thread_id) {
+ do {
+ mono_mutex_lock (&debugger_thread_exited_mutex);
+ if (!debugger_thread_exited) {
+#ifdef HOST_WIN32
+ if (WAIT_TIMEOUT == WaitForSingleObject(debugger_thread_exited_cond, 0)) {
+ mono_mutex_unlock (&debugger_thread_exited_mutex);
+ Sleep(1);
+ mono_mutex_lock (&debugger_thread_exited_mutex);
+ }
+#else
+ mono_cond_wait (&debugger_thread_exited_cond, &debugger_thread_exited_mutex);
+#endif
+ }
+ mono_mutex_unlock (&debugger_thread_exited_mutex);
+ } while (!debugger_thread_exited);
+ }
+
+#ifdef HOST_WIN32
+ shutdown (conn_fd, SD_BOTH);
+#else
+ shutdown (conn_fd, SHUT_RDWR);
+#endif
+}
+
static void
start_debugger_thread (void)
{
get_objref (MonoObject *obj)
{
ObjRef *ref;
+ GSList *reflist = NULL, *l;
+ int hash = 0;
if (obj == NULL)
return 0;
-#ifdef HAVE_SGEN_GC
- NOT_IMPLEMENTED;
-#endif
-
- /* Use a hash table with masked pointers to internalize object references */
- /* FIXME: This can grow indefinitely */
mono_loader_lock ();
if (!obj_to_objref)
obj_to_objref = g_hash_table_new (NULL, NULL);
+
+ /* FIXME: The tables can grow indefinitely */
- ref = g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)));
- /* ref might refer to a different object with the same addr which was GCd */
- if (ref && mono_gchandle_get_target (ref->handle) == obj) {
- mono_loader_unlock ();
- return ref;
+ if (mono_gc_is_moving ()) {
+ /*
+ * Objects can move, so use a hash table mapping hash codes to lists of
+ * ObjRef structures.
+ */
+ hash = mono_object_hash (obj);
+
+ reflist = g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (hash));
+ for (l = reflist; l; l = l->next) {
+ ref = l->data;
+ if (ref && mono_gchandle_get_target (ref->handle) == obj) {
+ mono_loader_unlock ();
+ return ref;
+ }
+ }
+ } else {
+ /* Use a hash table with masked pointers to internalize object references */
+ ref = g_hash_table_lookup (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)));
+ /* ref might refer to a different object with the same addr which was GCd */
+ if (ref && mono_gchandle_get_target (ref->handle) == obj) {
+ mono_loader_unlock ();
+ return ref;
+ }
}
ref = g_new0 (ObjRef, 1);
ref->handle = mono_gchandle_new_weakref (obj, FALSE);
g_hash_table_insert (objrefs, GINT_TO_POINTER (ref->id), ref);
- g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref);
+
+ if (mono_gc_is_moving ()) {
+ reflist = g_slist_append (reflist, ref);
+ g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (hash), reflist);
+ } else {
+ g_hash_table_insert (obj_to_objref, GINT_TO_POINTER (~((gsize)obj)), ref);
+ }
mono_loader_unlock ();
typedef struct {
/* Maps runtime structure -> Id */
GHashTable *val_to_id [ID_NUM];
+ /* Classes whose class load event has been sent */
+ GHashTable *loaded_classes;
} AgentDomainInfo;
/* Maps id -> Id */
for (i = 0; i < ID_NUM; ++i)
if (info->val_to_id [i])
g_hash_table_destroy (info->val_to_id [i]);
+ g_hash_table_destroy (info->loaded_classes);
g_free (info);
}
mono_loader_unlock ();
}
+/*
+ * Called when deferred debugger session is attached,
+ * after the VM start event has been sent successfully
+ */
+static void
+mono_debugger_agent_on_attach (void)
+{
+ /* Emit load events for currently loaded appdomains, assemblies, and types */
+ mono_loader_lock ();
+ g_hash_table_foreach (domains, emit_appdomain_load, NULL);
+ mono_g_hash_table_foreach (tid_to_thread, emit_thread_start, NULL);
+ mono_assembly_foreach (emit_assembly_load, NULL);
+ mono_loader_unlock ();
+}
+
+static AgentDomainInfo* get_agent_domain_info (MonoDomain *domain)
+{
+ AgentDomainInfo *info = NULL;
+
+ mono_domain_lock (domain);
+
+ info = domain_jit_info (domain)->agent_info;
+ if (!info) {
+ info = domain_jit_info (domain)->agent_info = g_new0 (AgentDomainInfo, 1);
+ info->loaded_classes = g_hash_table_new (mono_aligned_addr_hash, NULL);
+ }
+
+ mono_domain_unlock (domain);
+
+ return info;
+}
+
static int
get_id (MonoDomain *domain, IdType type, gpointer val)
{
mono_loader_lock ();
+ info = get_agent_domain_info (domain);
+
mono_domain_lock (domain);
- if (!domain_jit_info (domain)->agent_info)
- domain_jit_info (domain)->agent_info = g_new0 (AgentDomainInfo, 1);
- info = domain_jit_info (domain)->agent_info;
if (info->val_to_id [type] == NULL)
info->val_to_id [type] = g_hash_table_new (mono_aligned_addr_hash, NULL);
data.last_frame_set = FALSE;
if (sigctx) {
mono_arch_sigctx_to_monoctx (sigctx, &ctx);
- mono_jit_walk_stack_from_ctx_in_thread (get_last_frame, mono_domain_get (), &ctx, FALSE, tls->thread, mono_get_lmf (), &data);
+ mono_walk_stack (get_last_frame, mono_domain_get (), &ctx, MONO_UNWIND_DEFAULT, tls->thread, mono_get_lmf (), &data);
}
if (data.last_frame_set) {
memcpy (&tls->async_last_frame, &data.last_frame, sizeof (StackFrameInfo));
#ifdef HOST_WIN32
QueueUserAPC (notify_thread_apc, thread->handle, NULL);
#else
- pthread_kill ((pthread_t) tid, mono_thread_get_abort_signal ());
+ mono_thread_kill (thread, mono_thread_get_abort_signal ());
#endif
}
if (WAIT_TIMEOUT == WaitForSingleObject(suspend_cond, 0))
{
mono_mutex_unlock (&suspend_mutex);
- Sleep(0);
+ Sleep(1);
mono_mutex_lock (&suspend_mutex);
}
else
return NULL;
}
+/*
+ * find_next_seq_point_for_native_offset:
+ *
+ * Find the first sequence point after NATIVE_OFFSET.
+ */
+static SeqPoint*
+find_next_seq_point_for_native_offset (MonoDomain *domain, MonoMethod *method, gint32 native_offset, MonoSeqPointInfo **info)
+{
+ MonoSeqPointInfo *seq_points;
+ int i;
+
+ mono_domain_lock (domain);
+ seq_points = g_hash_table_lookup (domain_jit_info (domain)->seq_points, method);
+ mono_domain_unlock (domain);
+ g_assert (seq_points);
+
+ *info = seq_points;
+
+ for (i = 0; i < seq_points->len; ++i) {
+ if (seq_points->seq_points [i].native_offset >= native_offset)
+ return &seq_points->seq_points [i];
+ }
+
+ return NULL;
+}
+
+/*
+ * find_prev_seq_point_for_native_offset:
+ *
+ * Find the first sequence point before NATIVE_OFFSET.
+ */
+static SeqPoint*
+find_prev_seq_point_for_native_offset (MonoDomain *domain, MonoMethod *method, gint32 native_offset, MonoSeqPointInfo **info)
+{
+ MonoSeqPointInfo *seq_points;
+ int i;
+
+ mono_domain_lock (domain);
+ seq_points = g_hash_table_lookup (domain_jit_info (domain)->seq_points, method);
+ mono_domain_unlock (domain);
+ g_assert (seq_points);
+
+ *info = seq_points;
+
+ for (i = seq_points->len - 1; i >= 0; --i) {
+ if (seq_points->seq_points [i].native_offset <= native_offset)
+ return &seq_points->seq_points [i];
+ }
+
+ return NULL;
+}
+
/*
* find_seq_point:
*
{
ComputeFramesUserData *ud = user_data;
StackFrame *frame;
- MonoMethod *method;
+ MonoMethod *method, *actual_method;
if (info->type != FRAME_TYPE_MANAGED) {
if (info->type == FRAME_TYPE_DEBUGGER_INVOKE) {
method = info->ji->method;
else
method = info->method;
+ actual_method = info->actual_method;
if (!method || (method->wrapper_type && method->wrapper_type != MONO_WRAPPER_DYNAMIC_METHOD))
return FALSE;
info->il_offset = mono_debug_il_offset_from_address (method, info->domain, info->native_offset);
}
- DEBUG (1, fprintf (log_file, "\tFrame: %s %d %d %d\n", mono_method_full_name (method, TRUE), info->native_offset, info->il_offset, info->managed));
+ DEBUG (1, fprintf (log_file, "\tFrame: %s:%x(%x) %d\n", mono_method_full_name (method, TRUE), info->il_offset, info->native_offset, info->managed));
if (!info->managed && method->wrapper_type != MONO_WRAPPER_DYNAMIC_METHOD) {
/*
frame = g_new0 (StackFrame, 1);
frame->method = method;
+ frame->actual_method = actual_method;
frame->il_offset = info->il_offset;
+ frame->native_offset = info->native_offset;
if (ctx) {
frame->ctx = *ctx;
frame->has_ctx = TRUE;
return FALSE;
}
+static gboolean
+process_filter_frame (StackFrameInfo *info, MonoContext *ctx, gpointer user_data)
+{
+ ComputeFramesUserData *ud = user_data;
+
+ /*
+ * 'tls->filter_ctx' is the location of the throw site.
+ *
+ * mono_walk_stack() will never actually hit the throw site, but unwind
+ * directly from the filter to the call site; we abort stack unwinding here
+ * once this happens and resume from the throw site.
+ */
+
+ if (MONO_CONTEXT_GET_SP (ctx) >= MONO_CONTEXT_GET_SP (&ud->tls->filter_ctx))
+ return TRUE;
+
+ return process_frame (info, ctx, user_data);
+}
+
static void
compute_frame_info (MonoInternalThread *thread, DebuggerTlsData *tls)
{
} if (!tls->really_suspended && tls->has_async_ctx) {
/* Have to use the state saved by the signal handler */
process_frame (&tls->async_last_frame, NULL, &user_data);
- mono_jit_walk_stack_from_ctx_in_thread (process_frame, tls->domain, &tls->async_ctx, FALSE, thread, tls->async_lmf, &user_data);
+ mono_walk_stack (process_frame, tls->domain, &tls->async_ctx, MONO_UNWIND_DEFAULT, thread, tls->async_lmf, &user_data);
+ } else if (tls->has_filter_ctx) {
+ /*
+ * We are inside an exception filter.
+ *
+ * First we add all the frames from inside the filter; 'tls->ctx' has the current context.
+ */
+ if (tls->has_context)
+ mono_walk_stack (process_filter_frame, tls->domain, &tls->ctx, MONO_UNWIND_DEFAULT, thread, tls->lmf, &user_data);
+ /*
+ * After that, we resume unwinding from the location where the exception has been thrown.
+ */
+ mono_walk_stack (process_frame, tls->domain, &tls->filter_ctx, MONO_UNWIND_DEFAULT, thread, tls->filter_lmf, &user_data);
} else if (tls->has_context) {
- mono_jit_walk_stack_from_ctx_in_thread (process_frame, tls->domain, &tls->ctx, FALSE, thread, tls->lmf, &user_data);
+ mono_walk_stack (process_frame, tls->domain, &tls->ctx, MONO_UNWIND_DEFAULT, thread, tls->lmf, &user_data);
} else {
// FIXME:
tls->frame_count = 0;
tls->frames_up_to_date = TRUE;
}
+/*
+ * GHFunc to emit an appdomain creation event
+ * @param key Don't care
+ * @param value A loaded appdomain
+ * @param user_data Don't care
+ */
+static void
+emit_appdomain_load (gpointer key, gpointer value, gpointer user_data)
+{
+ process_profiler_event (EVENT_KIND_APPDOMAIN_CREATE, value);
+ g_hash_table_foreach (get_agent_domain_info (value)->loaded_classes, emit_type_load, NULL);
+}
+
+/*
+ * GHFunc to emit a thread start event
+ * @param key A thread id
+ * @param value A thread object
+ * @param user_data Don't care
+ */
+static void
+emit_thread_start (gpointer key, gpointer value, gpointer user_data)
+{
+ if (GPOINTER_TO_INT (key) != debugger_thread_id)
+ process_profiler_event (EVENT_KIND_THREAD_START, value);
+}
+
+/*
+ * GFunc to emit an assembly load event
+ * @param value A loaded assembly
+ * @param user_data Don't care
+ */
+static void
+emit_assembly_load (gpointer value, gpointer user_data)
+{
+ process_profiler_event (EVENT_KIND_ASSEMBLY_LOAD, value);
+}
+
+/*
+ * GFunc to emit a type load event
+ * @param value A loaded type
+ * @param user_data Don't care
+ */
+static void
+emit_type_load (gpointer key, gpointer value, gpointer user_data)
+{
+ process_profiler_event (EVENT_KIND_TYPE_LOAD, value);
+}
+
+
/*
* EVENT HANDLING
*/
Buffer buf;
GSList *l;
MonoDomain *domain = mono_domain_get ();
- MonoThread *thread;
+ MonoThread *thread = NULL;
+ gboolean send_success = FALSE;
- if (!inited)
+ if (!inited) {
+ DEBUG (2, fprintf (log_file, "Debugger agent not initialized yet: dropping %s\n", event_to_string (event)));
return;
+ }
- if (!vm_start_event_sent && event != EVENT_KIND_VM_START)
+ if (!vm_start_event_sent && event != EVENT_KIND_VM_START) {
// FIXME: We miss those events
+ DEBUG (2, fprintf (log_file, "VM start event not sent yet: dropping %s\n", event_to_string (event)));
return;
+ }
- if (vm_death_event_sent)
+ if (vm_death_event_sent) {
+ DEBUG (2, fprintf (log_file, "VM death event has been sent: dropping %s\n", event_to_string (event)));
return;
+ }
- if (mono_runtime_is_shutting_down () && event != EVENT_KIND_VM_DEATH)
+ if (mono_runtime_is_shutting_down () && event != EVENT_KIND_VM_DEATH) {
+ DEBUG (2, fprintf (log_file, "Mono runtime is shutting down: dropping %s\n", event_to_string (event)));
return;
+ }
- if (disconnected)
+ if (disconnected) {
+ DEBUG (2, fprintf (log_file, "Debugger client is not connected: dropping %s\n", event_to_string (event)));
return;
+ }
if (events == NULL)
return;
-
- if (debugger_thread_id == GetCurrentThreadId () && event != EVENT_KIND_VM_DEATH)
- // FIXME: Send these with a NULL thread, don't suspend the current thread
- return;
+
+ if (agent_config.defer) {
+ /* Make sure the thread id is always set when doing deferred debugging */
+ if (debugger_thread_id == GetCurrentThreadId ())
+ thread = mono_thread_get_main ();
+ else thread = mono_thread_current ();
+ }
buffer_init (&buf, 128);
buffer_add_byte (&buf, suspend_policy);
buffer_add_byte (&buf, event); // event kind
buffer_add_int (&buf, GPOINTER_TO_INT (l->data)); // request id
- thread = mono_thread_current ();
-
- if (event == EVENT_KIND_VM_START)
+ if (event == EVENT_KIND_VM_START && arg != NULL)
thread = arg;
- else if (event == EVENT_KIND_THREAD_START)
- g_assert (mono_thread_internal_current () == arg);
buffer_add_objid (&buf, (MonoObject*)thread); // thread
if (event == EVENT_KIND_VM_START) {
suspend_policy = agent_config.suspend ? SUSPEND_POLICY_ALL : SUSPEND_POLICY_NONE;
- start_debugger_thread ();
+ if (!agent_config.defer)
+ start_debugger_thread ();
}
if (event == EVENT_KIND_VM_DEATH) {
vm_death_event_sent = TRUE;
-
suspend_policy = SUSPEND_POLICY_NONE;
}
suspend_vm ();
}
- send_packet (CMD_SET_EVENT, CMD_COMPOSITE, &buf);
+ send_success = send_packet (CMD_SET_EVENT, CMD_COMPOSITE, &buf);
+
+ buffer_free (&buf);
g_slist_free (events);
events = NULL;
- if (event == EVENT_KIND_VM_START)
+ if (!send_success) {
+ DEBUG (2, fprintf (log_file, "Sending command %s failed.\n", event_to_string (event)));
+ return;
+ }
+
+ if (event == EVENT_KIND_VM_START) {
vm_start_event_sent = TRUE;
-
+ if (agent_config.defer)
+ mono_debugger_agent_on_attach ();
+ }
+
DEBUG (1, fprintf (log_file, "[%p] Sent event %s, suspend=%d.\n", (gpointer)GetCurrentThreadId (), event_to_string (event), suspend_policy));
- buffer_free (&buf);
switch (suspend_policy) {
case SUSPEND_POLICY_NONE:
runtime_initialized (MonoProfiler *prof)
{
process_profiler_event (EVENT_KIND_VM_START, mono_thread_current ());
-}
+ if (agent_config.defer)
+ start_debugger_thread ();
+}
static void
runtime_shutdown (MonoProfiler *prof)
}
static void
-thread_startup (MonoProfiler *prof, intptr_t tid)
+thread_startup (MonoProfiler *prof, uintptr_t tid)
{
MonoInternalThread *thread = mono_thread_internal_current ();
MonoInternalThread *old_thread;
// FIXME: Free this somewhere
tls = g_new0 (DebuggerTlsData, 1);
tls->resume_event = CreateEvent (NULL, FALSE, FALSE, NULL);
- MONO_GC_REGISTER_ROOT (tls->thread);
+ MONO_GC_REGISTER_ROOT_SINGLE (tls->thread);
tls->thread = thread;
TlsSetValue (debugger_tls_id, tls);
}
static void
-thread_end (MonoProfiler *prof, intptr_t tid)
+thread_end (MonoProfiler *prof, uintptr_t tid)
{
MonoInternalThread *thread;
DebuggerTlsData *tls = NULL;
static void
appdomain_unload (MonoProfiler *prof, MonoDomain *domain)
{
+ clear_breakpoints_for_domain (domain);
+
+ mono_loader_lock ();
/* Invalidate each thread's frame stack */
mono_g_hash_table_foreach (thread_to_tls, invalidate_each_thread, NULL);
+ mono_loader_unlock ();
+
process_profiler_event (EVENT_KIND_APPDOMAIN_UNLOAD, domain);
}
process_profiler_event (EVENT_KIND_ASSEMBLY_UNLOAD, assembly);
clear_event_requests_for_assembly (assembly);
+ clear_types_for_assembly (assembly);
}
static void
gpointer stackptr = __builtin_frame_address (1);
#endif
- if (ss_req == NULL || stackptr != ss_invoke_addr || ss_req->thread != mono_thread_internal_current ())
+ if (!embedding || ss_req == NULL || stackptr != ss_invoke_addr || ss_req->thread != mono_thread_internal_current ())
return;
/*
send_type_load (MonoClass *klass)
{
gboolean type_load = FALSE;
+ MonoDomain *domain = mono_domain_get ();
+ AgentDomainInfo *info = NULL;
mono_loader_lock ();
- if (!g_hash_table_lookup (loaded_classes, klass)) {
+ info = get_agent_domain_info (domain);
+ mono_domain_lock (domain);
+
+ if (!g_hash_table_lookup (info->loaded_classes, klass)) {
type_load = TRUE;
- g_hash_table_insert (loaded_classes, klass, klass);
+ g_hash_table_insert (info->loaded_classes, klass, klass);
}
+
+ mono_domain_unlock (domain);
mono_loader_unlock ();
if (type_load)
- process_profiler_event (EVENT_KIND_TYPE_LOAD, klass);
+ emit_type_load (klass, klass, NULL);
}
static void
}
mono_loader_unlock ();
- if (assembly)
+ if (assembly) {
process_profiler_event (EVENT_KIND_ASSEMBLY_LOAD, assembly);
- else
+ } else {
break;
- }
-
- if (!vm_start_event_sent) {
- /* Save these so they can be sent after the vm start event */
- mono_loader_lock ();
- g_ptr_array_add (pending_type_loads, method->klass);
- mono_loader_unlock ();
- } else {
- /* Send all pending type load events */
- MonoClass *klass;
- while (TRUE) {
- klass = NULL;
- mono_loader_lock ();
- if (pending_type_loads->len > 0) {
- klass = g_ptr_array_index (pending_type_loads, 0);
- g_ptr_array_remove_index (pending_type_loads, 0);
- }
- mono_loader_unlock ();
- if (klass)
- send_type_load (klass);
- else
- break;
}
-
- send_type_load (method->klass);
}
+ send_type_load (method->klass);
+
if (!result)
add_pending_breakpoints (method, jinfo);
}
long il_offset, native_offset;
guint8 *ip;
MonoJitInfo *ji;
+ MonoDomain *domain;
} BreakpointInstance;
/*
bp_locs = g_hash_table_new (NULL, NULL);
}
-static void
-breakpoints_cleanup (void)
-{
- int i;
-
- mono_loader_lock ();
-
- for (i = 0; i < breakpoints->len; ++i)
- g_free (g_ptr_array_index (breakpoints, i));
-
- g_ptr_array_free (breakpoints, TRUE);
- g_hash_table_destroy (bp_locs);
-
- mono_loader_unlock ();
-}
-
/*
* insert_breakpoint:
*
* JI.
*/
static void
-insert_breakpoint (MonoSeqPointInfo *seq_points, MonoJitInfo *ji, MonoBreakpoint *bp)
+insert_breakpoint (MonoSeqPointInfo *seq_points, MonoDomain *domain, MonoJitInfo *ji, MonoBreakpoint *bp, MonoError *error)
{
int i, count;
gint32 il_offset = -1, native_offset;
BreakpointInstance *inst;
+ if (error)
+ mono_error_init (error);
+
native_offset = 0;
for (i = 0; i < seq_points->len; ++i) {
il_offset = seq_points->seq_points [i].il_offset;
}
if (i == seq_points->len) {
- /* Have to handle this somehow */
- g_error ("Unable to insert breakpoint at %s:%d, seq_points=%d\n", mono_method_full_name (ji->method, TRUE), bp->il_offset, seq_points->len);
+ char *s = g_strdup_printf ("Unable to insert breakpoint at %s:%d, seq_points=%d\n", mono_method_full_name (ji->method, TRUE), bp->il_offset, seq_points->len);
+ if (error) {
+ mono_error_set_error (error, MONO_ERROR_GENERIC, "%s", s);
+ g_free (s);
+ return;
+ } else {
+ g_error ("%s", s);
+ g_free (s);
+ }
}
inst = g_new0 (BreakpointInstance, 1);
inst->native_offset = native_offset;
inst->ip = (guint8*)ji->code_start + native_offset;
inst->ji = ji;
+ inst->domain = domain;
mono_loader_lock ();
continue;
g_assert (seq_points);
- insert_breakpoint (seq_points, ji, bp);
+ insert_breakpoint (seq_points, domain, ji, bp, NULL);
}
}
}
static void
-set_bp_in_method (MonoDomain *domain, MonoMethod *method, MonoSeqPointInfo *seq_points, MonoBreakpoint *bp)
+set_bp_in_method (MonoDomain *domain, MonoMethod *method, MonoSeqPointInfo *seq_points, MonoBreakpoint *bp, MonoError *error)
{
gpointer code;
MonoJitInfo *ji;
+ if (error)
+ mono_error_init (error);
+
code = mono_jit_find_compiled_method_with_jit_info (domain, method, &ji);
if (!code) {
/* Might be AOTed code */
- code = mono_aot_get_method (domain, method);
- g_assert (code);
- ji = mono_jit_info_table_find (domain, code);
- g_assert (ji);
- }
- g_assert (code);
-
- insert_breakpoint (seq_points, ji, bp);
-}
-
-typedef struct
-{
- MonoBreakpoint *bp;
- MonoDomain *domain;
-} SetBpUserData;
-
-static void
-set_bp_in_method_cb (gpointer key, gpointer value, gpointer user_data)
-{
- MonoMethod *method = key;
- MonoSeqPointInfo *seq_points = value;
- SetBpUserData *ud = user_data;
- MonoBreakpoint *bp = ud->bp;
- MonoDomain *domain = ud->domain;
+ code = mono_aot_get_method (domain, method);
+ g_assert (code);
+ ji = mono_jit_info_table_find (domain, code);
+ g_assert (ji);
+ }
+ g_assert (code);
- if (bp_matches_method (bp, method))
- set_bp_in_method (domain, method, seq_points, bp);
+ insert_breakpoint (seq_points, domain, ji, bp, error);
}
static void
-set_bp_in_domain (gpointer key, gpointer value, gpointer user_data)
-{
- MonoDomain *domain = key;
- MonoBreakpoint *bp = user_data;
- SetBpUserData ud;
-
- ud.bp = bp;
- ud.domain = domain;
-
- mono_domain_lock (domain);
- g_hash_table_foreach (domain_jit_info (domain)->seq_points, set_bp_in_method_cb, &ud);
- mono_domain_unlock (domain);
-}
+clear_breakpoint (MonoBreakpoint *bp);
/*
* set_breakpoint:
* METHOD can be NULL, in which case a breakpoint is placed in all methods.
* METHOD can also be a generic method definition, in which case a breakpoint
* is placed in all instances of the method.
+ * If ERROR is non-NULL, then it is set and NULL is returnd if some breakpoints couldn't be
+ * inserted.
*/
static MonoBreakpoint*
-set_breakpoint (MonoMethod *method, long il_offset, EventRequest *req)
+set_breakpoint (MonoMethod *method, long il_offset, EventRequest *req, MonoError *error)
{
MonoBreakpoint *bp;
+ GHashTableIter iter, iter2;
+ MonoDomain *domain;
+ MonoMethod *m;
+ MonoSeqPointInfo *seq_points;
+
+ if (error)
+ mono_error_init (error);
// FIXME:
// - suspend/resume the vm to prevent code patching problems
mono_loader_lock ();
- g_hash_table_foreach (domains, set_bp_in_domain, bp);
+ g_hash_table_iter_init (&iter, domains);
+ while (g_hash_table_iter_next (&iter, (void**)&domain, NULL)) {
+ mono_domain_lock (domain);
+
+ g_hash_table_iter_init (&iter2, domain_jit_info (domain)->seq_points);
+ while (g_hash_table_iter_next (&iter2, (void**)&m, (void**)&seq_points)) {
+ if (bp_matches_method (bp, m))
+ set_bp_in_method (domain, m, seq_points, bp, error);
+ }
+
+ mono_domain_unlock (domain);
+ }
mono_loader_unlock ();
g_ptr_array_add (breakpoints, bp);
mono_loader_unlock ();
+ if (error && !mono_error_ok (error)) {
+ clear_breakpoint (bp);
+ return NULL;
+ }
+
return bp;
}
g_free (bp);
}
+static void
+breakpoints_cleanup (void)
+{
+ int i;
+
+ mono_loader_lock ();
+ i = 0;
+ while (i < event_requests->len) {
+ EventRequest *req = g_ptr_array_index (event_requests, i);
+
+ if (req->event_kind == EVENT_KIND_BREAKPOINT) {
+ clear_breakpoint (req->info);
+ g_ptr_array_remove_index_fast (event_requests, i);
+ g_free (req);
+ } else {
+ i ++;
+ }
+ }
+
+ for (i = 0; i < breakpoints->len; ++i)
+ g_free (g_ptr_array_index (breakpoints, i));
+
+ g_ptr_array_free (breakpoints, TRUE);
+ g_hash_table_destroy (bp_locs);
+
+ breakpoints = NULL;
+ bp_locs = NULL;
+
+ mono_loader_unlock ();
+}
+
+/*
+ * clear_breakpoints_for_domain:
+ *
+ * Clear breakpoint instances which reference DOMAIN.
+ */
+static void
+clear_breakpoints_for_domain (MonoDomain *domain)
+{
+ int i, j;
+
+ /* This could be called after shutdown */
+ if (!breakpoints)
+ return;
+
+ mono_loader_lock ();
+ for (i = 0; i < breakpoints->len; ++i) {
+ MonoBreakpoint *bp = g_ptr_array_index (breakpoints, i);
+
+ j = 0;
+ while (j < bp->children->len) {
+ BreakpointInstance *inst = g_ptr_array_index (bp->children, j);
+
+ if (inst->domain == domain) {
+ remove_breakpoint (inst);
+
+ g_free (inst);
+
+ g_ptr_array_remove_index_fast (bp->children, j);
+ } else {
+ j ++;
+ }
+ }
+ }
+ mono_loader_unlock ();
+}
+
static gboolean
breakpoint_matches_assembly (MonoBreakpoint *bp, MonoAssembly *assembly)
{
/* Process single step requests */
for (i = 0; i < ss_reqs_orig->len; ++i) {
EventRequest *req = g_ptr_array_index (ss_reqs_orig, i);
- SingleStepReq *ss_req = bp->req->info;
+ SingleStepReq *ss_req = req->info;
gboolean hit = TRUE;
MonoSeqPointInfo *info;
SeqPoint *sp;
g_ptr_array_add (ss_reqs, req);
/* Start single stepping again from the current sequence point */
- ss_start (ss_req, ji->method, sp, info, ctx, NULL);
+ ss_start (ss_req, ji->method, sp, info, ctx, NULL, FALSE);
}
if (ss_reqs->len > 0)
mono_arch_sigctx_to_monoctx (sigctx, &ctx);
memcpy (&tls->handler_ctx, &ctx, sizeof (MonoContext));
+#ifdef MONO_ARCH_HAVE_SETUP_RESUME_FROM_SIGNAL_HANDLER_CTX
+ mono_arch_setup_resume_sighandler_ctx (&ctx, func);
+#else
MONO_CONTEXT_SET_IP (&ctx, func);
+#endif
mono_arch_monoctx_to_sigctx (&ctx, sigctx);
#ifdef PPC_USES_FUNCTION_DESCRIPTOR
resume_from_signal_handler (sigctx, process_breakpoint);
}
+static const char*
+ss_depth_to_string (StepDepth depth)
+{
+ switch (depth) {
+ case STEP_DEPTH_OVER:
+ return "over";
+ case STEP_DEPTH_OUT:
+ return "out";
+ case STEP_DEPTH_INTO:
+ return "into";
+ default:
+ g_assert_not_reached ();
+ return NULL;
+ }
+}
+
static void
process_single_step_inner (DebuggerTlsData *tls, MonoContext *ctx)
{
return;
if (log_level > 0) {
- const char *depth = NULL;
-
ji = mini_jit_info_table_find (mono_domain_get (), (char*)ip, &domain);
- switch (ss_req->depth) {
- case STEP_DEPTH_OVER:
- depth = "over";
- break;
- case STEP_DEPTH_OUT:
- depth = "out";
- break;
- case STEP_DEPTH_INTO:
- depth = "into";
- break;
- default:
- g_assert_not_reached ();
- }
-
- DEBUG (1, fprintf (log_file, "[%p] Single step event (depth=%s) at %s (%p), sp %p, last sp %p\n", (gpointer)GetCurrentThreadId (), ss_req->depth == STEP_DEPTH_OVER ? "over" : "out", mono_method_full_name (ji->method, TRUE), MONO_CONTEXT_GET_IP (ctx), MONO_CONTEXT_GET_SP (ctx), ss_req->last_sp));
+ DEBUG (1, fprintf (log_file, "[%p] Single step event (depth=%s) at %s (%p), sp %p, last sp %p\n", (gpointer)GetCurrentThreadId (), ss_depth_to_string (ss_req->depth), mono_method_full_name (ji->method, TRUE), MONO_CONTEXT_GET_IP (ctx), MONO_CONTEXT_GET_SP (ctx), ss_req->last_sp));
}
/*
* Start the single stepping operation given by SS_REQ from the sequence point SP.
*/
static void
-ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint *sp, MonoSeqPointInfo *info, MonoContext *ctx, DebuggerTlsData *tls)
+ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint *sp, MonoSeqPointInfo *info, MonoContext *ctx, DebuggerTlsData *tls, gboolean step_to_catch)
{
- gboolean use_bp = FALSE;
int i, frame_index;
SeqPoint *next_sp;
MonoBreakpoint *bp;
/*
* Implement single stepping using breakpoints if possible.
*/
- if (ss_req->depth == STEP_DEPTH_OVER) {
+ if (step_to_catch) {
+ bp = set_breakpoint (method, sp->il_offset, ss_req->req, NULL);
+ ss_req->bps = g_slist_append (ss_req->bps, bp);
+ } else if (ss_req->depth == STEP_DEPTH_OVER) {
frame_index = 1;
/*
* Find the first sequence point in the current or in a previous frame which
}
if (sp && sp->next_len > 0) {
- use_bp = TRUE;
for (i = 0; i < sp->next_len; ++i) {
next_sp = &info->seq_points [sp->next [i]];
- bp = set_breakpoint (method, next_sp->il_offset, ss_req->req);
+ bp = set_breakpoint (method, next_sp->il_offset, ss_req->req, NULL);
ss_req->bps = g_slist_append (ss_req->bps, bp);
}
}
}
if (!ss_req->bps) {
+ DEBUG (1, printf ("[dbg] Turning on global single stepping.\n"));
ss_req->global = TRUE;
start_single_stepping ();
} else {
ss_create (MonoInternalThread *thread, StepSize size, StepDepth depth, EventRequest *req)
{
DebuggerTlsData *tls;
- MonoSeqPointInfo *info;
+ MonoSeqPointInfo *info = NULL;
SeqPoint *sp = NULL;
MonoMethod *method = NULL;
+ MonoDebugMethodInfo *minfo;
+ gboolean step_to_catch = FALSE;
if (suspend_count == 0)
return ERR_NOT_SUSPENDED;
return ERR_NOT_IMPLEMENTED;
}
+ DEBUG (1, printf ("[dbg] Starting single step of thread %p (depth=%s).\n", thread, ss_depth_to_string (depth)));
+
ss_req = g_new0 (SingleStepReq, 1);
ss_req->req = req;
ss_req->thread = thread;
g_assert (tls->has_context);
ss_req->start_sp = ss_req->last_sp = MONO_CONTEXT_GET_SP (&tls->ctx);
- if (ss_req->size == STEP_SIZE_LINE) {
+ if (tls->has_catch_ctx) {
+ gboolean res;
+ StackFrameInfo frame;
+ MonoContext new_ctx;
+ MonoLMF *lmf = NULL;
+
+ /*
+ * We are stopped at a throw site. Stepping should go to the catch site.
+ */
+
+ /* Find the the jit info for the catch context */
+ res = mono_find_jit_info_ext (mono_domain_get (), thread->jit_data, NULL, &tls->catch_ctx, &new_ctx, NULL, &lmf, NULL, &frame);
+ g_assert (res);
+ g_assert (frame.type == FRAME_TYPE_MANAGED);
+
+ /*
+ * Find the seq point corresponding to the landing site ip, which is the first seq
+ * point after ip.
+ */
+ sp = find_next_seq_point_for_native_offset (frame.domain, frame.method, frame.native_offset, &info);
+ g_assert (sp);
+
+ method = frame.method;
+
+ step_to_catch = TRUE;
+ /* This make sure the seq point is not skipped by process_single_step () */
+ ss_req->last_sp = NULL;
+ }
+
+ if (!step_to_catch && ss_req->size == STEP_SIZE_LINE) {
StackFrame *frame;
- MonoDebugMethodInfo *minfo;
/* Compute the initial line info */
compute_frame_info (thread, tls);
}
}
- if (ss_req->depth == STEP_DEPTH_OVER) {
+ if (!step_to_catch && ss_req->depth == STEP_DEPTH_OVER) {
StackFrame *frame;
compute_frame_info (thread, tls);
g_assert (tls->frame_count);
frame = tls->frames [0];
- if (frame->il_offset != -1) {
+ if (!method && frame->il_offset != -1) {
/* FIXME: Sort the table and use a binary search */
- sp = find_seq_point (frame->domain, frame->method, frame->il_offset, &info);
+ sp = find_prev_seq_point_for_native_offset (frame->domain, frame->method, frame->native_offset, &info);
g_assert (sp);
method = frame->method;
}
}
- ss_start (ss_req, method, sp, info, NULL, tls);
+ ss_start (ss_req, method, sp, info, NULL, tls, step_to_catch);
return 0;
}
GSList *events;
MonoJitInfo *ji;
EventInfo ei;
+ DebuggerTlsData *tls = NULL;
if (thread_to_tls != NULL) {
MonoInternalThread *thread = mono_thread_internal_current ();
- DebuggerTlsData *tls;
mono_loader_lock ();
tls = mono_g_hash_table_lookup (thread_to_tls, thread);
if (tls && tls->abort_requested)
return;
+ if (tls && tls->disable_breakpoints)
+ return;
}
memset (&ei, 0, sizeof (EventInfo));
events = create_event_list (EVENT_KIND_EXCEPTION, NULL, ji, &ei, &suspend_policy);
mono_loader_unlock ();
+ if (tls && catch_ctx) {
+ tls->catch_ctx = *catch_ctx;
+ tls->has_catch_ctx = TRUE;
+ }
+
process_event (EVENT_KIND_EXCEPTION, &ei, 0, throw_ctx, events, suspend_policy);
+
+ if (tls)
+ tls->has_catch_ctx = FALSE;
+}
+
+void
+mono_debugger_agent_begin_exception_filter (MonoException *exc, MonoContext *ctx, MonoContext *orig_ctx)
+{
+ DebuggerTlsData *tls;
+
+ if (!inited)
+ return;
+
+ tls = TlsGetValue (debugger_tls_id);
+ if (!tls)
+ return;
+
+ /*
+ * We're about to invoke an exception filter during the first pass of exception handling.
+ *
+ * 'ctx' is the context that'll get passed to the filter ('call_filter (ctx, ei->data.filter)'),
+ * 'orig_ctx' is the context where the exception has been thrown.
+ *
+ *
+ * See mcs/class/Mono.Debugger.Soft/Tests/dtest-excfilter.il for an example.
+ *
+ * If we're stopped in Filter(), normal stack unwinding would first unwind to
+ * the call site (line 37) and then continue to Main(), but it would never
+ * include the throw site (line 32).
+ *
+ * Since exception filters are invoked during the first pass of exception handling,
+ * the stack frames of the throw site are still intact, so we should include them
+ * in a stack trace.
+ *
+ * We do this here by saving the context of the throw site in 'tls->filter_ctx'.
+ *
+ * Exception filters are used by MonoDroid, where we want to stop inside a call filter,
+ * but report the location of the 'throw' to the user.
+ *
+ */
+
+ memcpy (&tls->filter_ctx, orig_ctx, sizeof (MonoContext));
+ tls->has_filter_ctx = TRUE;
+
+ tls->filter_lmf = mono_get_lmf ();
+ tls->domain = mono_domain_get ();
+}
+
+void
+mono_debugger_agent_end_exception_filter (MonoException *exc, MonoContext *ctx, MonoContext *orig_ctx)
+{
+ DebuggerTlsData *tls;
+
+ if (!inited)
+ return;
+
+ tls = TlsGetValue (debugger_tls_id);
+ if (!tls)
+ return;
+
+ tls->has_filter_ctx = FALSE;
}
/*
mono_loader_unlock ();
}
+/*
+ * type_comes_from_assembly:
+ *
+ * GHRFunc that returns TRUE if klass comes from assembly
+ */
+static gboolean
+type_comes_from_assembly (gpointer klass, gpointer also_klass, gpointer assembly)
+{
+ return (mono_class_get_image ((MonoClass*)klass) == mono_assembly_get_image ((MonoAssembly*)assembly));
+}
+
+/*
+ * clear_types_for_assembly:
+ *
+ * Clears types from loaded_classes for a given assembly
+ */
+static void
+clear_types_for_assembly (MonoAssembly *assembly)
+{
+ MonoDomain *domain = mono_domain_get ();
+ AgentDomainInfo *info = NULL;
+
+ mono_loader_lock ();
+ info = get_agent_domain_info (domain);
+ g_hash_table_foreach_remove (info->loaded_classes, type_comes_from_assembly, assembly);
+ mono_loader_unlock ();
+}
+
static void
add_thread (gpointer key, gpointer value, gpointer user_data)
{
MonoObject *this, *res, *exc;
MonoDomain *domain;
guint8 *this_buf;
-#ifdef MONO_ARCH_HAVE_FIND_JIT_INFO_EXT
+#ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
MonoLMFExt ext;
#endif
* Add an LMF frame to link the stack frames on the invoke method with our caller.
*/
/* FIXME: Move this to arch specific code */
-#ifdef MONO_ARCH_HAVE_FIND_JIT_INFO_EXT
+#ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
if (invoke->has_ctx) {
MonoLMF **lmf_addr;
ext.lmf.previous_lmf = *(lmf_addr);
/* Mark that this is a MonoLMFExt */
ext.lmf.previous_lmf = (gpointer)(((gssize)ext.lmf.previous_lmf) | 2);
- ext.lmf.ebp = (gssize)&ext;
#elif defined(TARGET_POWERPC)
ext.lmf.previous_lmf = *(lmf_addr);
/* Mark that this is a MonoLMFExt */
tls->disable_breakpoints = FALSE;
-#ifdef MONO_ARCH_HAVE_FIND_JIT_INFO_EXT
+#ifdef MONO_ARCH_SOFT_DEBUG_SUPPORTED
if (invoke->has_ctx)
mono_set_lmf ((gpointer)(((gssize)ext.lmf.previous_lmf) & ~3));
#endif
}
mono_loader_unlock ();
- // FIXME: Count resumes
- resume_vm ();
+ while (suspend_count > 0)
+ resume_vm ();
disconnected = TRUE;
+ vm_start_event_sent = FALSE;
break;
case CMD_VM_EXIT: {
MonoInternalThread *thread;
event_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
{
int err;
+ MonoError error;
switch (command) {
case CMD_EVENT_REQUEST_SET: {
if (req->event_kind == EVENT_KIND_BREAKPOINT) {
g_assert (method);
- req->info = set_breakpoint (method, location, req);
+ req->info = set_breakpoint (method, location, req, &error);
+ if (!mono_error_ok (&error)) {
+ g_free (req);
+ DEBUG(1, fprintf (log_file, "[dbg] Failed to set breakpoint: %s\n", mono_error_get_message (&error)));
+ mono_error_cleanup (&error);
+ return ERR_NO_SEQ_POINT_AT_IL_OFFSET;
+ }
} else if (req->event_kind == EVENT_KIND_STEP) {
g_assert (step_thread_id);
return err;
}
} else if (req->event_kind == EVENT_KIND_METHOD_ENTRY) {
- req->info = set_breakpoint (NULL, METHOD_ENTRY_IL_OFFSET, req);
+ req->info = set_breakpoint (NULL, METHOD_ENTRY_IL_OFFSET, req, NULL);
} else if (req->event_kind == EVENT_KIND_METHOD_EXIT) {
- req->info = set_breakpoint (NULL, METHOD_EXIT_IL_OFFSET, req);
+ req->info = set_breakpoint (NULL, METHOD_EXIT_IL_OFFSET, req, NULL);
} else if (req->event_kind == EVENT_KIND_EXCEPTION) {
} else {
if (req->nmodifiers) {
buffer_add_cattrs (buf, domain, klass->image, attr_klass, cinfo);
break;
}
- case CMD_TYPE_GET_VALUES: {
+ case CMD_TYPE_GET_VALUES:
+ case CMD_TYPE_GET_VALUES_2: {
guint8 *val;
MonoClassField *f;
MonoVTable *vtable;
MonoClass *k;
int len, i;
gboolean found;
+ MonoThread *thread_obj;
+ MonoInternalThread *thread = NULL;
+ guint32 special_static_type;
+
+ if (command == CMD_TYPE_GET_VALUES_2) {
+ int objid = decode_objid (p, &p, end);
+ int err;
+
+ err = get_object (objid, (MonoObject**)&thread_obj);
+ if (err)
+ return err;
+
+ thread = THREAD_TO_INTERNAL (thread_obj);
+ }
len = decode_int (p, &p, end);
for (i = 0; i < len; ++i) {
if (!(f->type->attrs & FIELD_ATTRIBUTE_STATIC))
return ERR_INVALID_FIELDID;
- if (mono_class_field_is_special_static (f))
- return ERR_INVALID_FIELDID;
+ special_static_type = mono_class_field_get_special_static_type (f);
+ if (special_static_type != SPECIAL_STATIC_NONE) {
+ if (!(thread && special_static_type == SPECIAL_STATIC_THREAD))
+ return ERR_INVALID_FIELDID;
+ }
/* Check that the field belongs to the object */
found = FALSE;
vtable = mono_class_vtable (domain, f->parent);
val = g_malloc (mono_class_instance_size (mono_class_from_mono_type (f->type)));
- mono_field_static_get_value (vtable, f, val);
+ mono_field_static_get_value_for_thread (thread ? thread : mono_thread_internal_current (), vtable, f, val);
buffer_add_value (buf, f->type, val, domain);
g_free (val);
}
}
case CMD_METHOD_GET_LOCALS_INFO: {
int i, j, num_locals;
- char **local_names;
- int *local_indexes;
+ MonoDebugLocalsInfo *locals;
header = mono_method_get_header (method);
g_assert (header);
buffer_add_typeid (buf, domain, mono_class_from_mono_type (header->locals [i]));
/* Names */
- num_locals = mono_debug_lookup_locals (method, &local_names, &local_indexes);
+ locals = mono_debug_lookup_locals (method);
+ if (locals)
+ num_locals = locals->num_locals;
+ else
+ num_locals = 0;
for (i = 0; i < header->num_locals; ++i) {
for (j = 0; j < num_locals; ++j)
- if (local_indexes [j] == i)
+ if (locals->locals [j].index == i)
break;
if (j < num_locals)
- buffer_add_string (buf, local_names [j]);
+ buffer_add_string (buf, locals->locals [j].name);
else
buffer_add_string (buf, "");
}
- g_free (local_names);
- g_free (local_indexes);
- /* Live ranges */
- /* FIXME: This works because we set debug_options.mdb_optimizations */
+ /* Scopes */
for (i = 0; i < header->num_locals; ++i) {
- buffer_add_int (buf, 0);
- buffer_add_int (buf, header->code_size);
+ for (j = 0; j < num_locals; ++j)
+ if (locals->locals [j].index == i)
+ break;
+ if (j < num_locals && locals->locals [j].block) {
+ buffer_add_int (buf, locals->locals [j].block->start_offset);
+ buffer_add_int (buf, locals->locals [j].block->end_offset);
+ } else {
+ buffer_add_int (buf, 0);
+ buffer_add_int (buf, header->code_size);
+ }
}
mono_metadata_free_mh (header);
+ if (locals)
+ mono_debug_symfile_free_locals (locals);
+
break;
}
case CMD_METHOD_GET_INFO:
if (handle_class == mono_defaults.typehandle_class) {
buffer_add_byte (buf, TOKEN_TYPE_TYPE);
- buffer_add_typeid (buf, domain, mono_class_from_mono_type ((MonoType*)val));
+ if (method->wrapper_type == MONO_WRAPPER_DYNAMIC_METHOD)
+ buffer_add_typeid (buf, domain, (MonoClass *) val);
+ else
+ buffer_add_typeid (buf, domain, mono_class_from_mono_type ((MonoType*)val));
} else if (handle_class == mono_defaults.fieldhandle_class) {
buffer_add_byte (buf, TOKEN_TYPE_FIELD);
buffer_add_fieldid (buf, domain, val);
buffer_add_int (buf, tls->frame_count);
for (i = 0; i < tls->frame_count; ++i) {
buffer_add_int (buf, tls->frames [i]->id);
- buffer_add_methodid (buf, tls->frames [i]->domain, tls->frames [i]->method);
+ buffer_add_methodid (buf, tls->frames [i]->domain, tls->frames [i]->actual_method);
buffer_add_int (buf, tls->frames [i]->il_offset);
/*
* Instead of passing the frame type directly to the client, we associate
case CMD_THREAD_GET_ID:
buffer_add_long (buf, (guint64)(gsize)thread);
break;
+ case CMD_THREAD_GET_TID:
+ buffer_add_long (buf, (guint64)thread->tid);
+ break;
default:
return ERR_NOT_IMPLEMENTED;
}
}
jit = frame->jit;
- sig = mono_method_signature (frame->method);
+ sig = mono_method_signature (frame->actual_method);
switch (command) {
case CMD_STACK_FRAME_GET_VALUES: {
len = decode_int (p, &p, end);
- header = mono_method_get_header (frame->method);
+ header = mono_method_get_header (frame->actual_method);
for (i = 0; i < len; ++i) {
pos = decode_int (p, &p, end);
MonoObject *p = NULL;
buffer_add_value (buf, &mono_defaults.object_class->byval_arg, &p, frame->domain);
} else {
- add_var (buf, &frame->method->klass->this_arg, jit->this_var, &frame->ctx, frame->domain, TRUE);
+ add_var (buf, &frame->actual_method->klass->this_arg, jit->this_var, &frame->ctx, frame->domain, TRUE);
}
} else {
if (!sig->hasthis) {
MonoObject *p = NULL;
- buffer_add_value (buf, &frame->method->klass->byval_arg, &p, frame->domain);
+ buffer_add_value (buf, &frame->actual_method->klass->byval_arg, &p, frame->domain);
} else {
add_var (buf, &frame->method->klass->byval_arg, jit->this_var, &frame->ctx, frame->domain, TRUE);
}
MonoDebugVarInfo *var;
len = decode_int (p, &p, end);
- header = mono_method_get_header (frame->method);
+ header = mono_method_get_header (frame->actual_method);
for (i = 0; i < len; ++i) {
pos = decode_int (p, &p, end);
}
}
+static const char*
+cmd_to_string (CommandSet set, int command)
+{
+ switch (set) {
+ case CMD_SET_VM: {
+ switch (command) {
+ case CMD_VM_VERSION:
+ return "VERSION";
+ case CMD_VM_ALL_THREADS:
+ return "ALL_THREADS";
+ case CMD_VM_SUSPEND:
+ return "SUSPEND";
+ case CMD_VM_RESUME:
+ return "RESUME";
+ case CMD_VM_EXIT:
+ return "EXIT";
+ case CMD_VM_DISPOSE:
+ return "DISPOSE";
+ case CMD_VM_INVOKE_METHOD:
+ return "INVOKE_METHOD";
+ case CMD_VM_SET_PROTOCOL_VERSION:
+ return "SET_PROTOCOL_VERSION";
+ case CMD_VM_ABORT_INVOKE:
+ return "ABORT_INVOKE";
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static gboolean
+wait_for_attach (void)
+{
+ if (listen_fd == -1) {
+ DEBUG (1, fprintf (log_file, "[dbg] Invalid listening socket\n"));
+ return FALSE;
+ }
+
+ /* Block and wait for client connection */
+ conn_fd = transport_accept (listen_fd);
+ DEBUG (1, fprintf (log_file, "Accepted connection on %d\n", conn_fd));
+ if (conn_fd == -1) {
+ DEBUG (1, fprintf (log_file, "[dbg] Bad client connection\n"));
+ return FALSE;
+ }
+
+ /* Handshake */
+ disconnected = !transport_handshake ();
+ if (disconnected) {
+ DEBUG (1, fprintf (log_file, "Transport handshake failed!\n"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
/*
* debugger_thread:
*
Buffer buf;
ErrorCode err;
gboolean no_reply;
+ gboolean attach_failed = FALSE;
DEBUG (1, fprintf (log_file, "[dbg] Agent thread started, pid=%p\n", (gpointer)GetCurrentThreadId ()));
mono_thread_internal_current ()->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
mono_set_is_debugger_attached (TRUE);
-
- while (TRUE) {
+
+ if (agent_config.defer) {
+ if (!wait_for_attach ()) {
+ DEBUG (1, fprintf (log_file, "[dbg] Can't attach, aborting debugger thread.\n"));
+ attach_failed = TRUE; // Don't abort process when we can't listen
+ } else {
+ /* Send start event to client */
+ process_profiler_event (EVENT_KIND_VM_START, mono_thread_get_main ());
+ }
+ }
+
+ while (!attach_failed) {
res = recv_length (conn_fd, header, HEADER_LENGTH, 0);
/* This will break if the socket is closed during shutdown too */
g_assert (flags == 0);
- DEBUG (1, fprintf (log_file, "[dbg] Received command %s(%d), id=%d.\n", command_set_to_string (command_set), command, id));
+ if (log_level) {
+ const char *cmd_str;
+ char cmd_num [256];
+
+ cmd_str = cmd_to_string (command_set, command);
+ if (!cmd_str) {
+ sprintf (cmd_num, "%d", command);
+ cmd_str = cmd_num;
+ }
+
+ DEBUG (1, fprintf (log_file, "[dbg] Received command %s(%s), id=%d.\n", command_set_to_string (command_set), cmd_str, id));
+ }
data = g_malloc (len - HEADER_LENGTH);
if (len - HEADER_LENGTH > 0)
}
mono_set_is_debugger_attached (FALSE);
+
+#ifdef TARGET_WIN32
+ if (! (vm_death_event_sent || mono_runtime_is_shutting_down ()))
+#endif
+ {
+ mono_mutex_lock (&debugger_thread_exited_mutex);
+ debugger_thread_exited = TRUE;
+ mono_cond_signal (&debugger_thread_exited_cond);
+ mono_mutex_unlock (&debugger_thread_exited_mutex);
- mono_mutex_lock (&debugger_thread_exited_mutex);
- debugger_thread_exited = TRUE;
- mono_cond_signal (&debugger_thread_exited_cond);
- mono_mutex_unlock (&debugger_thread_exited_mutex);
-
- DEBUG (1, printf ("[dbg] Debugger thread exited.\n"));
-
+ DEBUG (1, printf ("[dbg] Debugger thread exited.\n"));
+
+ if (!attach_failed && command_set == CMD_SET_VM && command == CMD_VM_DISPOSE && !(vm_death_event_sent || mono_runtime_is_shutting_down ())) {
+ DEBUG (2, fprintf (log_file, "[dbg] Detached - restarting clean debugger thread.\n"));
+ start_debugger_thread ();
+ }
+ }
+
return 0;
}
void
mono_debugger_agent_parse_options (char *options)
{
- g_error ("This runtime is configure with the debugger agent disabled.");
+ g_error ("This runtime is configured with the debugger agent disabled.");
}
void
MonoContext *catch_ctx)
{
}
+
+void
+mono_debugger_agent_begin_exception_filter (MonoException *exc, MonoContext *ctx, MonoContext *orig_ctx)
+{
+}
+
+void
+mono_debugger_agent_end_exception_filter (MonoException *exc, MonoContext *ctx, MonoContext *orig_ctx)
+{
+}
+
#endif