Introduce MonoThreadUnwindState and a bunch overloads to mono_walk_stack.
[mono.git] / mono / mini / debugger-agent.c
index f7147dc0f3e56f32e0e928e61ba1a29fe071ee30..7a8a42b81df0f2e648372b9e374e11388d99b198 100644 (file)
@@ -72,6 +72,7 @@ int WSAAPI getnameinfo(const struct sockaddr*,socklen_t,char*,DWORD,
 #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"
 
@@ -84,6 +85,7 @@ int WSAAPI getnameinfo(const struct sockaddr*,socklen_t,char*,DWORD,
 #endif
 
 #ifndef DISABLE_DEBUGGER_AGENT
+
 #include <mono/io-layer/mono-mutex.h>
 
 /* Definitions to make backporting to 2.6 easier */
@@ -104,6 +106,7 @@ typedef struct {
        int timeout;
        char *launch;
        gboolean embedding;
+       gboolean defer;
 } AgentConfig;
 
 typedef struct
@@ -213,6 +216,10 @@ typedef struct {
 
        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.
         */
@@ -246,7 +253,7 @@ typedef struct {
 #define HEADER_LENGTH 11
 
 #define MAJOR_VERSION 2
-#define MINOR_VERSION 2
+#define MINOR_VERSION 3
 
 typedef enum {
        CMD_SET_VM = 1,
@@ -297,7 +304,8 @@ typedef enum {
        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 {
@@ -359,7 +367,8 @@ 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 {
@@ -420,6 +429,7 @@ 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 {
@@ -518,6 +528,7 @@ static AgentConfig agent_config;
 static gint32 inited;
 
 static int conn_fd;
+static int listen_fd;
 
 static int packet_id = 0;
 
@@ -552,15 +563,9 @@ static gboolean embedding;
 
 static FILE *log_file;
 
-/* Classes whose class load event has been sent */
-static GHashTable *loaded_classes;
-
 /* 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;
 
@@ -606,12 +611,20 @@ 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);
@@ -628,9 +641,11 @@ static void suspend_current (void);
 
 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 clear_types_for_assembly (MonoAssembly *assembly);
+static void process_profiler_event (EventKind event, gpointer arg);
 
 /* Submodule init/cleanup */
 static void breakpoints_init (void);
@@ -649,6 +664,7 @@ static ErrorCode ss_create (MonoInternalThread *thread, StepSize size, StepDepth
 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);
 
@@ -678,8 +694,9 @@ print_usage (void)
        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");
 }
 
@@ -712,6 +729,8 @@ mono_debugger_agent_parse_options (char *options)
        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 ++) {
@@ -751,6 +770,14 @@ mono_debugger_agent_parse_options (char *options)
                }
        }
 
+       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);
@@ -805,14 +832,13 @@ mono_debugger_agent_init (void)
        tid_to_thread_obj = mono_g_hash_table_new_type (NULL, NULL, MONO_HASH_VALUE_GC);
        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+");
@@ -900,48 +926,11 @@ mono_debugger_agent_cleanup (void)
        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);
-               while (!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);
@@ -965,6 +954,70 @@ recv_length (int fd, void *buf, int len, int flags)
        } 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:
  *
@@ -973,14 +1026,17 @@ recv_length (int fd, void *buf, int len, int flags)
 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);
@@ -988,7 +1044,7 @@ transport_connect (const char *host, int 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 */
@@ -997,12 +1053,20 @@ transport_connect (const char *host, int port)
 
                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;
@@ -1018,6 +1082,7 @@ transport_connect (const char *host, int port)
                                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));
@@ -1045,6 +1110,7 @@ transport_connect (const char *host, int port)
                                res = listen (sfd, 16);
                                if (res == -1)
                                        continue;
+                               listen_fd = sfd;
                                break;
                        }
 
@@ -1055,10 +1121,15 @@ transport_connect (const char *host, int port)
                         * http://msdn.microsoft.com/en-us/library/ms737931(VS.85).aspx
                         * only works with MSVC.
                         */
+#ifdef HAVE_GETADDRINFO
                        freeaddrinfo (result);
+#endif
 #endif
                }
 
+               if (agent_config.defer)
+                       return;
+
                DEBUG (1, fprintf (log_file, "Listening on %s:%d (timeout=%d ms)...\n", host, port, agent_config.timeout));
 
                if (agent_config.timeout) {
@@ -1076,15 +1147,17 @@ transport_connect (const char *host, int port)
                        }
                }
 
-               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,
@@ -1098,54 +1171,32 @@ transport_connect (const char *host, int port)
                        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
@@ -1162,6 +1213,60 @@ transport_send (guint8 *data, int len)
                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)
 {
@@ -1573,6 +1678,8 @@ typedef struct {
 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 */
@@ -1612,6 +1719,7 @@ mono_debugger_agent_free_domain_info (MonoDomain *domain)
                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);
        }
 
@@ -1633,6 +1741,39 @@ mono_debugger_agent_free_domain_info (MonoDomain *domain)
        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)
 {
@@ -1646,9 +1787,8 @@ get_id (MonoDomain *domain, IdType type, gpointer val)
 
        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;
+       info = get_agent_domain_info (domain);
+
        if (info->val_to_id [type] == NULL)
                info->val_to_id [type] = g_hash_table_new (mono_aligned_addr_hash, NULL);
 
@@ -1935,7 +2075,7 @@ mono_debugger_agent_thread_interrupt (void *sigctx, MonoJitInfo *ji)
 
        if (ji) {
                /* Running managed code, will be suspended by the single step code */
-               DEBUG (1, printf ("[%p] Received interrupt while at %s(%p), continuing.\n", (gpointer)GetCurrentThreadId (), ji->method->name, mono_arch_ip_from_context (sigctx)));
+               DEBUG (1, fprintf (log_file, "[%p] Received interrupt while at %s(%p), continuing.\n", (gpointer)GetCurrentThreadId (), ji->method->name, mono_arch_ip_from_context (sigctx)));
                return TRUE;
        } else {
                /* 
@@ -1951,7 +2091,7 @@ mono_debugger_agent_thread_interrupt (void *sigctx, MonoJitInfo *ji)
                        // FIXME: printf is not signal safe, but this is only used during
                        // debugger debugging
                        if (sigctx)
-                               DEBUG (1, printf ("[%p] Received interrupt while at %p, treating as suspended.\n", (gpointer)GetCurrentThreadId (), mono_arch_ip_from_context (sigctx)));
+                               DEBUG (1, fprintf (log_file, "[%p] Received interrupt while at %p, treating as suspended.\n", (gpointer)GetCurrentThreadId (), mono_arch_ip_from_context (sigctx)));
                        //save_thread_context (&ctx);
 
                        if (!tls->thread)
@@ -1971,7 +2111,7 @@ mono_debugger_agent_thread_interrupt (void *sigctx, MonoJitInfo *ji)
                        data.last_frame_set = FALSE;
                        if (sigctx) {
                                mono_arch_sigctx_to_monoctx (sigctx, &ctx);
-                               mono_walk_stack (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));
@@ -2064,6 +2204,15 @@ process_suspend (DebuggerTlsData *tls, MonoContext *ctx)
        guint8 *ip = MONO_CONTEXT_GET_IP (ctx);
        MonoJitInfo *ji;
 
+       if (mono_loader_lock_is_owned_by_self ()) {
+               /*
+                * Shortcut for the check in suspend_current (). This speeds up processing
+                * when executing long running code inside the loader lock, i.e. assembly load
+                * hooks.
+                */
+               return;
+       }
+
        if (debugger_thread_id == GetCurrentThreadId ())
                return;
 
@@ -2260,7 +2409,7 @@ suspend_current (void)
                if (WAIT_TIMEOUT == WaitForSingleObject(suspend_cond, 0))
                {
                        mono_mutex_unlock (&suspend_mutex);
-                       Sleep(0);
+                       Sleep(1);
                        mono_mutex_lock (&suspend_mutex);
                }
                else
@@ -2562,6 +2711,25 @@ process_frame (StackFrameInfo *info, MonoContext *ctx, gpointer user_data)
        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)
 {
@@ -2584,9 +2752,21 @@ 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_walk_stack (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_walk_stack (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;
@@ -2625,6 +2805,55 @@ compute_frame_info (MonoInternalThread *thread, DebuggerTlsData *tls)
        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
  */
@@ -2749,30 +2978,48 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx
        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 ();
+       } else {
+               if (debugger_thread_id == GetCurrentThreadId () && event != EVENT_KIND_VM_DEATH)
+                       // FIXME: Send these with a NULL thread, don't suspend the current thread
+                       return;
+       }
 
        buffer_init (&buf, 128);
        buffer_add_byte (&buf, suspend_policy);
@@ -2782,12 +3029,11 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx
                buffer_add_byte (&buf, event); // event kind
                buffer_add_int (&buf, GPOINTER_TO_INT (l->data)); // request id
 
-               thread = mono_thread_current ();
+               if (!thread)
+                       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
 
@@ -2832,12 +3078,12 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx
 
        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;
        }
 
@@ -2854,17 +3100,26 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx
                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:
@@ -2897,7 +3152,9 @@ static void
 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)
@@ -3008,9 +3265,13 @@ appdomain_load (MonoProfiler *prof, MonoDomain *domain, int result)
 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);
-       clear_breakpoints_for_domain (domain);
+       mono_loader_unlock ();
+       
        process_profiler_event (EVENT_KIND_APPDOMAIN_UNLOAD, domain);
 }
 
@@ -3102,15 +3363,23 @@ static void
 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)) {
+       mono_domain_lock (domain);
+
+       info = get_agent_domain_info (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
@@ -3133,37 +3402,15 @@ jit_end (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo, int result)
                }
                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);
 }
@@ -3222,12 +3469,15 @@ breakpoints_init (void)
  * JI.
  */
 static void
-insert_breakpoint (MonoSeqPointInfo *seq_points, MonoDomain *domain, 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;
@@ -3238,8 +3488,15 @@ insert_breakpoint (MonoSeqPointInfo *seq_points, MonoDomain *domain, MonoJitInfo
        }
 
        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);
@@ -3338,7 +3595,7 @@ add_pending_breakpoints (MonoMethod *method, MonoJitInfo *ji)
                                continue;
                        g_assert (seq_points);
 
-                       insert_breakpoint (seq_points, domain, ji, bp);
+                       insert_breakpoint (seq_points, domain, ji, bp, NULL);
                }
        }
 
@@ -3346,11 +3603,14 @@ add_pending_breakpoints (MonoMethod *method, MonoJitInfo *ji)
 }
 
 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 */
@@ -3361,42 +3621,11 @@ set_bp_in_method (MonoDomain *domain, MonoMethod *method, MonoSeqPointInfo *seq_
        }
        g_assert (code);
 
-       insert_breakpoint (seq_points, domain, 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;
-
-       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:
@@ -3405,11 +3634,20 @@ set_bp_in_domain (gpointer key, gpointer value, gpointer user_data)
  * 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
@@ -3427,7 +3665,18 @@ set_breakpoint (MonoMethod *method, long il_offset, EventRequest *req)
 
        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 ();
 
@@ -3435,6 +3684,11 @@ set_breakpoint (MonoMethod *method, long il_offset, EventRequest *req)
        g_ptr_array_add (breakpoints, bp);
        mono_loader_unlock ();
 
+       if (error && !mono_error_ok (error)) {
+               clear_breakpoint (bp);
+               return NULL;
+       }
+
        return bp;
 }
 
@@ -3749,6 +4003,22 @@ mono_debugger_agent_breakpoint_hit (void *sigctx)
        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)
 {
@@ -3779,25 +4049,9 @@ 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));
        }
 
        /*
@@ -4029,7 +4283,6 @@ ss_stop (SingleStepReq *ss_req)
 static void
 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;
@@ -4041,7 +4294,7 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint *sp, MonoSeqPointI
         * Implement single stepping using breakpoints if possible.
         */
        if (step_to_catch) {
-               bp = set_breakpoint (method, sp->il_offset, ss_req->req);
+               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;
@@ -4063,17 +4316,17 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint *sp, MonoSeqPointI
                }
 
                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, fprintf (log_file, "[dbg] Turning on global single stepping.\n"));
                ss_req->global = TRUE;
                start_single_stepping ();
        } else {
@@ -4101,10 +4354,12 @@ ss_create (MonoInternalThread *thread, StepSize size, StepDepth depth, EventRequ
 
        // FIXME: Multiple requests
        if (ss_req) {
-               DEBUG (0, printf ("Received a single step request while the previous one was still active.\n"));
+               DEBUG (0, fprintf (log_file, "Received a single step request while the previous one was still active.\n"));
                return ERR_NOT_IMPLEMENTED;
        }
 
+       DEBUG (1, fprintf (log_file, "[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;
@@ -4130,7 +4385,7 @@ ss_create (MonoInternalThread *thread, StepSize size, StepDepth depth, EventRequ
                 */
 
                /* 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, &frame);
+               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);
 
@@ -4292,6 +4547,64 @@ mono_debugger_agent_handle_exception (MonoException *exc, MonoContext *throw_ctx
                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;
+}
+
 /*
  * buffer_add_value_full:
  *
@@ -4754,8 +5067,12 @@ type_comes_from_assembly (gpointer klass, gpointer also_klass, gpointer assembly
 static void
 clear_types_for_assembly (MonoAssembly *assembly)
 {
+       MonoDomain *domain = mono_domain_get ();
+       AgentDomainInfo *info = NULL;
+
        mono_loader_lock ();
-       g_hash_table_foreach_remove (loaded_classes, type_comes_from_assembly, assembly);
+       info = get_agent_domain_info (domain);
+       g_hash_table_foreach_remove (info->loaded_classes, type_comes_from_assembly, assembly);
        mono_loader_unlock ();
 }
 
@@ -4790,7 +5107,7 @@ do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke)
                 * Invoke this method directly, currently only Environment.Exit () is supported.
                 */
                this = NULL;
-               DEBUG (1, printf ("[%p] Invoking method '%s' on receiver '%s'.\n", (gpointer)GetCurrentThreadId (), mono_method_full_name (invoke->method, TRUE), this ? this->vtable->klass->name : "<null>"));
+               DEBUG (1, fprintf (log_file, "[%p] Invoking method '%s' on receiver '%s'.\n", (gpointer)GetCurrentThreadId (), mono_method_full_name (invoke->method, TRUE), this ? this->vtable->klass->name : "<null>"));
                mono_runtime_invoke (invoke->method, NULL, invoke->args, &exc);
                g_assert_not_reached ();
        }
@@ -4821,7 +5138,7 @@ do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke)
        else
                this = NULL;
 
-       DEBUG (1, printf ("[%p] Invoking method '%s' on receiver '%s'.\n", (gpointer)GetCurrentThreadId (), mono_method_full_name (m, TRUE), this ? this->vtable->klass->name : "<null>"));
+       DEBUG (1, fprintf (log_file, "[%p] Invoking method '%s' on receiver '%s'.\n", (gpointer)GetCurrentThreadId (), mono_method_full_name (m, TRUE), this ? this->vtable->klass->name : "<null>"));
 
        if (this && this->vtable->domain != domain)
                NOT_IMPLEMENTED;
@@ -4932,8 +5249,15 @@ do_invoke_method (DebuggerTlsData *tls, Buffer *buf, InvokeData *invoke)
                } else if (MONO_TYPE_IS_REFERENCE (sig->ret)) {
                        buffer_add_value (buf, sig->ret, &res, domain);
                } else if (mono_class_from_mono_type (sig->ret)->valuetype) {
-                       g_assert (res);
-                       buffer_add_value (buf, sig->ret, mono_object_unbox (res), domain);
+                       if (mono_class_is_nullable (mono_class_from_mono_type (sig->ret))) {
+                               if (!res)
+                                       buffer_add_value (buf, &mono_defaults.object_class->byval_arg, &res, domain);
+                               else
+                                       buffer_add_value (buf, sig->ret, mono_object_unbox (res), domain);
+                       } else {
+                               g_assert (res);
+                               buffer_add_value (buf, sig->ret, mono_object_unbox (res), domain);
+                       }
                } else {
                        NOT_IMPLEMENTED;
                }
@@ -5015,7 +5339,7 @@ invoke_method (void)
                tls->resume_count -= invoke->suspend_count;
        }
 
-       DEBUG (1, printf ("[%p] Invoke finished, resume_count = %d.\n", (gpointer)GetCurrentThreadId (), tls->resume_count));
+       DEBUG (1, fprintf (log_file, "[%p] Invoke finished, resume_count = %d.\n", (gpointer)GetCurrentThreadId (), tls->resume_count));
 
        /*
         * Take the loader lock to avoid race conditions with CMD_VM_ABORT_INVOKE:
@@ -5108,9 +5432,10 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf)
                }
                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;
@@ -5147,15 +5472,14 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf)
                wait_for_suspend ();
 
                env_class = mono_class_from_name (mono_defaults.corlib, "System", "Environment");
-               g_assert (env_class);
-               exit_method = mono_class_get_method_from_name (env_class, "Exit", 1);
-               g_assert (exit_method);
+               if (env_class)
+                       exit_method = mono_class_get_method_from_name (env_class, "Exit", 1);
 
                mono_loader_lock ();
                thread = mono_g_hash_table_find (tid_to_thread, is_really_suspended, NULL);
                mono_loader_unlock ();
 
-               if (thread) {
+               if (thread && exit_method) {
                        mono_loader_lock ();
                        tls = mono_g_hash_table_lookup (thread_to_tls, thread);
                        mono_loader_unlock ();
@@ -5299,6 +5623,7 @@ static ErrorCode
 event_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
 {
        int err;
+       MonoError error;
 
        switch (command) {
        case CMD_EVENT_REQUEST_SET: {
@@ -5381,7 +5706,13 @@ event_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
                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);
 
@@ -5397,9 +5728,9 @@ event_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
                                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) {
@@ -5899,13 +6230,28 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint
                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) {
@@ -5915,8 +6261,11 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint
 
                        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;
@@ -5931,7 +6280,7 @@ type_commands_internal (int command, MonoClass *klass, MonoDomain *domain, guint
 
                        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);
                }
@@ -6259,7 +6608,10 @@ method_commands_internal (int command, MonoMethod *method, MonoDomain *domain, g
 
                        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);
@@ -6392,6 +6744,9 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
        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;
        }
@@ -6786,6 +7141,67 @@ command_set_to_string (CommandSet command_set)
        }
 }
 
+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:
  *
@@ -6801,6 +7217,7 @@ debugger_thread (void *arg)
        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 ()));
 
@@ -6811,8 +7228,18 @@ debugger_thread (void *arg)
        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 */
@@ -6830,7 +7257,18 @@ debugger_thread (void *arg)
 
                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)
@@ -6904,14 +7342,19 @@ debugger_thread (void *arg)
        }
 
        mono_set_is_debugger_attached (FALSE);
-
+       
        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, fprintf (log_file, "[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;
 }
 
@@ -6920,7 +7363,7 @@ debugger_thread (void *arg)
 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
@@ -6954,5 +7397,16 @@ mono_debugger_agent_handle_exception (MonoException *ext, MonoContext *throw_ctx
                                                                          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