< prev index next >

src/jdk.jdwp.agent/share/native/libjdwp/eventHandler.c

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -266,11 +266,11 @@
     return JNI_FALSE;
 }
 
 /* BREAKPOINT, METHOD_ENTRY and SINGLE_STEP events are covered by
  * the co-location of events policy. Of these three co-located
- * events, METHOD_ENTRY is  always reported first and BREAKPOINT
+ * events, METHOD_ENTRY is always reported first and BREAKPOINT
  * is always reported last. Here are the possible combinations and
  * their order:
  *
  * (p1) METHOD_ENTRY, BREAKPOINT (existing)
  * (p2) METHOD_ENTRY, BREAKPOINT (new)

@@ -531,25 +531,180 @@
 }
 
 /* Garbage Collection Happened */
 static unsigned int garbageCollected = 0;
 
-/* The JVMTI generic event callback. Each event is passed to a sequence of
+/*
+ * Run the event through each HandlerNode's filter, and if it passes, call the HandlerNode's
+ * HandlerFunction for the event, and then report all accumulated events to the debugger.
+ */
+static void
+filterAndHandleEvent(JNIEnv *env, EventInfo *evinfo, EventIndex ei,
+                     struct bag *eventBag, jbyte eventSessionID)
+{
+    debugMonitorEnter(handlerLock);
+    {
+        HandlerNode *node;
+        char        *classname;
+
+        /* We must keep track of all classes prepared to know what's unloaded */
+        if (evinfo->ei == EI_CLASS_PREPARE) {
+            classTrack_addPreparedClass(env, evinfo->clazz);
+        }
+
+        node = getHandlerChain(ei)->first;
+        classname = getClassname(evinfo->clazz);
+
+        /* Filter the event over each handler node. */
+        while (node != NULL) {
+            /* Save next so handlers can remove themselves. */
+            HandlerNode *next = NEXT(node);
+            jboolean shouldDelete;
+
+            /* passesFilter() will set evinfo->matchesFiber true if appropriate. */
+            if (eventFilterRestricted_passesFilter(env, classname,
+                                                   evinfo, node,
+                                                   &shouldDelete, JNI_FALSE /* filterOnly */)) {
+                HandlerFunction func = HANDLER_FUNCTION(node);
+                if (func == NULL) {
+                    EXIT_ERROR(AGENT_ERROR_INTERNAL,"handler function NULL");
+                }
+                /* Handle the event by calling the event handler. */
+                (*func)(env, evinfo, node, eventBag);
+            }
+            if (shouldDelete) {
+                /* We can safely free the node now that we are done using it. */
+                (void)freeHandler(node);
+            }
+            node = next;
+        }
+        jvmtiDeallocate(classname);
+    }
+    debugMonitorExit(handlerLock);
+
+    /*
+     * The events destined for the debugger were accumulated in eventBag. Report all these events.
+     */
+    if (eventBag != NULL) {
+        reportEvents(env, eventSessionID, evinfo->thread, evinfo->ei,
+                     evinfo->clazz, evinfo->method, evinfo->location, eventBag);
+    }
+}
+
+/*
+ * Called when we are not notifying the debugging of all fibers, and we are seeing an event
+ * on a fiber that we have not notified the debugger about yet. If the event passes the event
+ * filters, then we will notify the debugger with a THREAD_START and add it to our list of
+ * fibers. Otherwise we ignore it.
+ */
+static void
+filterAndAddFiber(JNIEnv *env, EventInfo *evinfo, EventIndex ei, jbyte eventSessionID,
+                  jthread thread, jthread fiber)
+{
+    jboolean needToAddFiber = JNI_FALSE; /* Assume we won't need to add the fiber. */;
+
+    /*
+     * Although we have received a JVMTI event for a fiber that we have not added yet,
+     * we only need to add it if we are going to pass the fiber on to the debugger. This
+     * might not end up happening due to event filtering. For example, we got a breakpoint
+     * on a carrier thread running a fiber, but the breakpoint HandlerNode specifies that
+     * event must be on a certain thread, so the event may end up being dropped. Therefore
+     * we need to do a dry run with the filtering to see if we really need to add this fiber.
+     */
+    {
+        HandlerNode *node = getHandlerChain(ei)->first;
+        char        *classname  = getClassname(evinfo->clazz);
+
+        debugMonitorEnter(handlerLock);
+
+        /* Filter the event over each handler node. */
+        while (node != NULL) {
+            /* Save next so handlers can remove themselves */
+            HandlerNode *next = NEXT(node);
+            jboolean shouldDelete;
+
+            /* passesFilter() will set evinfo->matchesFiber true if appropriate. */
+            if (eventFilterRestricted_passesFilter(env, classname,
+                                                   evinfo, node,
+                                                   &shouldDelete, JNI_TRUE /* filterOnly */)) {
+                if (evinfo->matchesFiber) {
+                    /* If we match even one filter and it matches on the fiber, then we need
+                     * to add the fiber. */
+                    needToAddFiber = JNI_TRUE;
+                    break;
+                }
+            }
+            JDI_ASSERT(!shouldDelete);
+            node = next;
+        }
+
+        debugMonitorExit(handlerLock);
+        jvmtiDeallocate(classname);
+    }
+
+    if (needToAddFiber) {
+        /* Make sure this fiber gets added to the fibers list. */
+        threadControl_addFiber(fiber);
+
+        /*
+         * When the FIBER_SCHEDULED event arrived for this fiber, we ignored it since we don't
+         * want to notify the debugger about fibers until there is a non-fiber event that
+         * arrives on it (like a breakpoint). Now that this has happened, we need to send
+         * a FIBER_SCHEDULED event (which will be converted into a THREAD_START event) so
+         * the debugger will know about the fiber. Otherwise it will be unhappy when it gets
+         * an event for a fiber that it never got a THREAD_START event for.
+         */
+        EventInfo info;
+        struct bag *eventBag = eventHelper_createEventBag();
+
+        JDI_ASSERT(evinfo->fiber != NULL);
+        (void)memset(&info,0,sizeof(info));
+        info.ei         = EI_FIBER_SCHEDULED;
+        info.thread     = thread;
+        info.fiber      = fiber;
+        info.matchesFiber = JNI_TRUE;
+
+        /* Note: filterAndHandleEvent() expects EI_THREAD_START instead of EI_FIBER_SCHEDULED. */
+        filterAndHandleEvent(env, &info, EI_THREAD_START, eventBag, eventSessionID);
+        JDI_ASSERT(bagSize(eventBag) == 0);
+        bagDestroyBag(eventBag);
+    }
+}
+
+/*
+ * The JVMTI generic event callback. Each event is passed to a sequence of
  * handlers in a chain until the chain ends or one handler
  * consumes the event.
  */
 static void
-event_callback(JNIEnv *env, EventInfo *evinfo)
+event_callback_helper(JNIEnv *env, EventInfo *evinfo)
 {
     struct bag *eventBag;
     jbyte eventSessionID = currentSessionID; /* session could change */
     jthrowable currentException;
     jthread thread;
+    EventIndex ei = evinfo->ei;
 
-    LOG_MISC(("event_callback(): ei=%s", eventText(evinfo->ei)));
+    LOG_MISC(("event_callback(): ei=%s", eventText(ei)));
     log_debugee_location("event_callback()", evinfo->thread, evinfo->method, evinfo->location);
 
+    if (evinfo->thread != NULL) {
+        /* fiber fixme: ignore all events on the fiber helper thread. Remove this when
+         * the need for helper threads goes away.
+         */
+        jclass threadClass = JNI_FUNC_PTR(env, GetObjectClass)(env, evinfo->thread);
+        if (JNI_FUNC_PTR(env, IsSameObject)(env, threadClass, gdata->innocuousThreadClass)) {
+            return;
+        }
+    }
+
+    /* fiber fixme: little hack to ignore THREAD_START events while we are creating a fiber
+     * helper thread. The need for this will eventually go away. */
+    if (gdata->ignoreEvents) {
+        return;
+    }
+
     /* We want to preserve any current exception that might get
      * wiped out during event handling (e.g. JNI calls). We have
      * to rely on space for the local reference on the current
      * frame because doing a PushLocalFrame here might itself
      * generate an exception.

@@ -592,19 +747,29 @@
         }
     }
 
     thread = evinfo->thread;
     if (thread != NULL) {
+        if (gdata->fibersSupported) {
+            /*
+             * Get the event fiber if one is running on this thread and has not already
+             * been set, which is the case for fiber related JVMTI events.
+             */
+            if (evinfo->fiber == NULL) {
+                evinfo->fiber = getThreadFiber(thread);
+            }
+        }
+
         /*
          * Record the fact that we're entering an event
          * handler so that thread operations (status, interrupt,
          * stop) can be done correctly and so that thread
          * resources can be allocated.  This must be done before
          * grabbing any locks.
          */
-        eventBag = threadControl_onEventHandlerEntry(eventSessionID,
-                                 evinfo->ei, thread, currentException);
+        eventBag = threadControl_onEventHandlerEntry(
+            eventSessionID, evinfo, currentException);
         if ( eventBag == NULL ) {
             jboolean invoking;
             do {
                 /* The event has been 'handled' and this
                  * thread is about to continue, but it may

@@ -630,55 +795,32 @@
              */
             eventBag = NULL;  /* to shut up lint */
         }
     }
 
-    debugMonitorEnter(handlerLock);
-    {
-        HandlerNode *node;
-        char        *classname;
-
-        /* We must keep track of all classes prepared to know what's unloaded */
-        if (evinfo->ei == EI_CLASS_PREPARE) {
-            classTrack_addPreparedClass(env, evinfo->clazz);
-        }
-
-        node = getHandlerChain(evinfo->ei)->first;
-        classname = getClassname(evinfo->clazz);
-
-        while (node != NULL) {
-            /* save next so handlers can remove themselves */
-            HandlerNode *next = NEXT(node);
-            jboolean shouldDelete;
-
-            if (eventFilterRestricted_passesFilter(env, classname,
-                                                   evinfo, node,
-                                                   &shouldDelete)) {
-                HandlerFunction func;
-
-                func = HANDLER_FUNCTION(node);
-                if ( func == NULL ) {
-                    EXIT_ERROR(AGENT_ERROR_INTERNAL,"handler function NULL");
-                }
-                (*func)(env, evinfo, node, eventBag);
-            }
-            if (shouldDelete) {
-                /* We can safely free the node now that we are done
-                 * using it.
-                 */
-                (void)freeHandler(node);
+    /* We want the fiber scheduled/terminated events to mimic thread start/end events */
+    if (ei == EI_FIBER_SCHEDULED) {
+        ei = EI_THREAD_START;
+    }
+    if (ei == EI_FIBER_TERMINATED) {
+        ei = EI_THREAD_END;
+    }
+
+    if (gdata->fibersSupported) {
+        /* Add the fiber if we haven't added it before. */
+        if (evinfo->fiber != NULL && !threadControl_isKnownFiber(evinfo->fiber)) {
+            if (gdata->notifyDebuggerOfAllFibers) {
+                /* Make sure this fiber gets added to the fibers list. */
+                threadControl_addFiber(evinfo->fiber);
+            } else {
+                /* Add this fiber if it passes the event filters. */
+                filterAndAddFiber(env, evinfo, ei, eventSessionID, thread, evinfo->fiber);
             }
-            node = next;
         }
-        jvmtiDeallocate(classname);
     }
-    debugMonitorExit(handlerLock);
 
-    if (eventBag != NULL) {
-        reportEvents(env, eventSessionID, thread, evinfo->ei,
-                evinfo->clazz, evinfo->method, evinfo->location, eventBag);
-    }
+    filterAndHandleEvent(env, evinfo, ei, eventBag, eventSessionID);
 
     /* we are continuing after VMDeathEvent - now we are dead */
     if (evinfo->ei == EI_VM_DEATH) {
         gdata->vmDead = JNI_TRUE;
     }

@@ -708,10 +850,20 @@
     if (thread != NULL) {
         threadControl_onEventHandlerExit(evinfo->ei, thread, eventBag);
     }
 }
 
+static void
+event_callback(JNIEnv *env, EventInfo *evinfo)
+{
+    /* fiber fixme: There are a bunch of WITH_LOCAL_REFS that we can remove now that
+     * we are doing one here. */
+    WITH_LOCAL_REFS(env, 64) {
+        event_callback_helper(env, evinfo);
+    } END_WITH_LOCAL_REFS(env);
+}
+
 /* Returns a local ref to the declaring class for an object. */
 static jclass
 getObjectClass(jobject object)
 {
     jclass clazz;

@@ -1292,10 +1444,160 @@
     debugLoop_sync();
 
     LOG_MISC(("END cbVMDeath"));
 }
 
+/* Event callback for JVMTI_EVENT_FIBER_SCHEDULED */
+static void JNICALL
+cbFiberScheduled(jvmtiEnv *jvmti_env, JNIEnv *env,
+                 jthread thread, jthread fiber)
+{
+    EventInfo info;
+
+    LOG_CB(("cbFiberScheduled: thread=%p", thread));
+    /*tty_message("cbFiberScheduled: thread=%p", thread);*/
+    JDI_ASSERT(gdata->fibersSupported);
+
+    /*
+     * Now would be a good time to cache the ThreadGroup for fibers (carrier threads)
+     * if we haven't already.
+     *
+     * fiber fixme: the is kind of a hack. What happens if custom
+     * scheduler uses carrier threads in multple group? What if as a result of this a
+     * Fiber can move between thread groups? We should have a dedicated
+     * thread group for all fibers, or allow a fiber to set its ThreadGroup.
+     */
+    if (gdata->fiberThreadGroup == NULL) {
+        jvmtiThreadInfo info;
+        jvmtiError error;
+
+        (void)memset(&info, 0, sizeof(info));
+        error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo)
+            (gdata->jvmti, thread, &info);
+
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error, "could not get fiber ThreadGroup");
+        } else {
+            debugMonitorEnter(callbackBlock); /* fiber fixme: any monitor will do, but there probably is a better choice. */
+            /* Make sure there wasn't a race before setting. saveGlobalRef() will assert if the
+             * target is not NULL. */
+            if (gdata->fiberThreadGroup == NULL) {
+                saveGlobalRef(env, info.thread_group, &gdata->fiberThreadGroup);
+            }
+            debugMonitorExit(callbackBlock);
+        }
+    }
+
+    /* Ignore FIBER_SCHEDULED events unless we are notifying the debugger of all fibers. */
+    if (!gdata->notifyDebuggerOfAllFibers) {
+        return;
+    }
+
+    BEGIN_CALLBACK() {
+        (void)memset(&info,0,sizeof(info));
+        info.ei         = EI_FIBER_SCHEDULED;
+        info.thread     = thread;
+        info.fiber      = fiber;
+        event_callback(env, &info);
+    } END_CALLBACK();
+
+    LOG_MISC(("END cbFiberScheduled"));
+}
+
+/* Event callback for JVMTI_EVENT_FIBER_TERMINATED */
+static void JNICALL
+cbFiberTerminated(jvmtiEnv *jvmti_env, JNIEnv *env,
+                  jthread thread, jthread fiber)
+{
+    EventInfo info;
+
+    LOG_CB(("cbFiberTerminated: thread=%p", thread));
+    /*tty_message("cbFiberTerminated: thread=%p", thread);*/
+    JDI_ASSERT(gdata->fibersSupported);
+
+    if (!gdata->notifyDebuggerOfAllFibers && !threadControl_isKnownFiber(fiber)) {
+        /* This is not a fiber we are tracking, so don't deliver a FIBER_TERMINATED event for it. */
+        return;
+    }
+
+    BEGIN_CALLBACK() {
+        (void)memset(&info,0,sizeof(info));
+        info.ei         = EI_FIBER_TERMINATED;
+        info.thread     = thread;
+        info.fiber      = fiber;
+        event_callback(env, &info);
+    } END_CALLBACK();
+
+    LOG_MISC(("END cbFiberTerminated"));
+}
+
+/* Event callback for JVMTI_EVENT_FIBER_MOUNT */
+static void JNICALL
+cbFiberMount(jvmtiEnv *jvmti_env, JNIEnv *env,
+             jthread thread, jthread fiber)
+{
+    LOG_CB(("cbFiberMount: thread=%p", thread));
+    /*tty_message("cbFiberMount: thread=%p", thread);*/
+    JDI_ASSERT(gdata->fibersSupported);
+
+    /* Ignore FIBER_MOUNT events unless we are doing fiber debugging. */
+    if (!gdata->fibersSupported) {
+        return;
+    }
+
+    threadControl_mountFiber(fiber, thread, currentSessionID);
+
+    LOG_MISC(("END cbFiberMount"));
+}
+
+/* Event callback for JVMTI_EVENT_FIBER_UNMOUNT */
+static void JNICALL
+cbFiberUnmount(jvmtiEnv *jvmti_env, JNIEnv *env,
+               jthread thread, jthread fiber)
+{
+    LOG_CB(("cbFiberUnmount: thread=%p", thread));
+    /*tty_message("cbFiberUnmount: thread=%p", thread);*/
+    JDI_ASSERT(gdata->fibersSupported);
+
+    /* Ignore FIBER_UNMOUNT events unless we are doing fiber debugging. */
+    if (!gdata->fibersSupported) {
+        return;
+    }
+
+    threadControl_unmountFiber(fiber, thread);
+
+    LOG_MISC(("END cbFiberUnmount"));
+}
+
+/* Event callback for JVMTI_EVENT_CONTINUATION_RUN */
+static void JNICALL
+cbContinuationRun(jvmtiEnv *jvmti_env, JNIEnv *env,
+                  jthread thread, jint continuation_frame_count)
+{
+    LOG_CB(("cbContinuationRun: thread=%p", thread));
+    //tty_message("cbContinuationRun: thread=%p continuation_frame_count=%d", thread, continuation_frame_count);
+    JDI_ASSERT(gdata->fibersSupported);
+
+    threadControl_continuationRun(thread, continuation_frame_count);
+
+    LOG_MISC(("END cbContinuationRun"));
+}
+
+/* Event callback for JVMTI_EVENT_CONTINUATION_YIELD */
+static void JNICALL
+cbContinuationYield(jvmtiEnv *jvmti_env, JNIEnv *env,
+                    jthread thread, jint continuation_frame_count)
+{
+    LOG_CB(("cbContinuationYield: thread=%p", thread));
+    //tty_message("cbContinuationYield: thread=%p continuation_frame_count=%d", thread, continuation_frame_count);
+    JDI_ASSERT(gdata->fibersSupported);
+
+    threadControl_continuationYield(thread, continuation_frame_count);
+
+    LOG_MISC(("END cbContinuationYield"));
+}
+
 /**
  * Delete this handler (do not delete permanent handlers):
  * Deinsert handler from active list,
  * make it inactive, and free it's memory
  * Assumes handlerLock held.

@@ -1480,10 +1782,43 @@
     error = threadControl_setEventMode(JVMTI_ENABLE,
                                        EI_GC_FINISH, NULL);
     if (error != JVMTI_ERROR_NONE) {
         EXIT_ERROR(error,"Can't enable garbage collection finish events");
     }
+    /* Only enable fiber events if fiber support is enabled. */
+    if (gdata->fibersSupported) {
+        error = threadControl_setEventMode(JVMTI_ENABLE,
+                                           EI_FIBER_SCHEDULED, NULL);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error,"Can't enable fiber scheduled events");
+        }
+        error = threadControl_setEventMode(JVMTI_ENABLE,
+                                           EI_FIBER_TERMINATED, NULL);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error,"Can't enable fiber terminated events");
+        }
+        error = threadControl_setEventMode(JVMTI_ENABLE,
+                                           EI_FIBER_MOUNT, NULL);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error,"Can't enable fiber mount events");
+        }
+        error = threadControl_setEventMode(JVMTI_ENABLE,
+                                           EI_FIBER_UNMOUNT, NULL);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error,"Can't enable fiber unmount events");
+        }
+        error = threadControl_setEventMode(JVMTI_ENABLE,
+                                           EI_CONTINUATION_RUN, NULL);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error,"Can't enable continuation run events");
+        }
+        error = threadControl_setEventMode(JVMTI_ENABLE,
+                                           EI_CONTINUATION_YIELD, NULL);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error,"Can't enable continuation yield events");
+        }
+    }
 
     (void)memset(&(gdata->callbacks),0,sizeof(gdata->callbacks));
     /* Event callback for JVMTI_EVENT_SINGLE_STEP */
     gdata->callbacks.SingleStep                 = &cbSingleStep;
     /* Event callback for JVMTI_EVENT_BREAKPOINT */

@@ -1522,10 +1857,22 @@
     gdata->callbacks.VMInit                     = &cbVMInit;
     /* Event callback for JVMTI_EVENT_VM_DEATH */
     gdata->callbacks.VMDeath                    = &cbVMDeath;
     /* Event callback for JVMTI_EVENT_GARBAGE_COLLECTION_FINISH */
     gdata->callbacks.GarbageCollectionFinish    = &cbGarbageCollectionFinish;
+    /* Event callback for JVMTI_EVENT_FIBER_SCHEDULED */
+    gdata->callbacks.FiberScheduled             = &cbFiberScheduled;
+    /* Event callback for JVMTI_EVENT_FIBER_TERMINATED */
+    gdata->callbacks.FiberTerminated            = &cbFiberTerminated;
+    /* Event callback for JVMTI_EVENT_FIBER_MOUNT */
+    gdata->callbacks.FiberMount                 = &cbFiberMount;
+    /* Event callback for JVMTI_EVENT_FIBER_UNMOUNT */
+    gdata->callbacks.FiberUnmount               = &cbFiberUnmount;
+    /* Event callback for JVMTI_EVENT_CONTINUATION_RUN */
+    gdata->callbacks.ContinuationRun            = &cbContinuationRun;
+    /* Event callback for JVMTI_EVENT_CONTINUATION_YIELD */
+    gdata->callbacks.ContinuationYield          = &cbContinuationYield;
 
     error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventCallbacks)
                 (gdata->jvmti, &(gdata->callbacks), sizeof(gdata->callbacks));
     if (error != JVMTI_ERROR_NONE) {
         EXIT_ERROR(error,"Can't set event callbacks");

@@ -1707,5 +2054,45 @@
 {
     return installHandler(node,
                           standardHandlers_defaultHandler(node->ei),
                           JNI_TRUE);
 }
+
+/***** debugging *****/
+
+void
+eventHandler_dumpAllHandlers(jboolean dumpPermanent)
+{
+    int ei;
+    for (ei = EI_min; ei <= EI_max; ++ei) {
+        eventHandler_dumpHandlers(ei, dumpPermanent);
+    }
+}
+
+void
+eventHandler_dumpHandlers(EventIndex ei, jboolean dumpPermanent)
+{
+  HandlerNode *nextNode;
+  nextNode = getHandlerChain(ei)->first;
+  if (nextNode != NULL) {
+      tty_message("\nHandlers for %s(%d)", eventIndex2EventName(ei), ei);
+      while (nextNode != NULL) {
+          HandlerNode *node = nextNode;
+          nextNode = NEXT(node);
+          
+          if (node->permanent && !dumpPermanent) {
+              continue; // ignore permanent handlers
+          }
+
+          tty_message("node(%p) handlerID(%d) suspendPolicy(%d) permanent(%d)",
+                      node, node->handlerID, node->suspendPolicy, node->permanent);
+          eventFilter_dumpHandlerFilters(node);
+      }
+  }
+}
+
+void
+eventHandler_dumpHandler(HandlerNode *node)
+{
+  tty_message("Handler for %s(%d)\n", eventIndex2EventName(node->ei), node->ei);
+  eventFilter_dumpHandlerFilters(node);
+}
< prev index next >