< prev index next >

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

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1998, 2005, 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

@@ -30,24 +30,10 @@
 #include "threadControl.h"
 #include "SDE.h"
 
 static jrawMonitorID stepLock;
 
-static jint
-getFrameCount(jthread thread)
-{
-    jint count = 0;
-    jvmtiError error;
-
-    error = JVMTI_FUNC_PTR(gdata->jvmti,GetFrameCount)
-                    (gdata->jvmti, thread, &count);
-    if (error != JVMTI_ERROR_NONE) {
-        EXIT_ERROR(error, "getting frame count");
-    }
-    return count;
-}
-
 /*
  * Most enabling/disabling of JVMTI events happens implicitly through
  * the inserting and freeing of handlers for those events. Stepping is
  * different because requested steps are usually not identical to JVMTI steps.
  * They usually require multiple events step, and otherwise, before they

@@ -84,10 +70,18 @@
     if (error != JVMTI_ERROR_NONE) {
         EXIT_ERROR(error, "disabling single step");
     }
 }
 
+void stepControl_enableStepping(jthread thread) {
+  enableStepping(thread);
+}
+
+void stepControl_disableStepping(jthread thread) {
+  disableStepping(thread);
+}
+
 static jvmtiError
 getFrameLocation(jthread thread,
         jclass *pclazz, jmethodID *pmethod, jlocation *plocation)
 {
     jvmtiError error;

@@ -171,11 +165,11 @@
      * Initial values that may be changed below
      */
     step->fromLine = -1;
     step->fromNative = JNI_FALSE;
     step->frameExited = JNI_FALSE;
-    step->fromStackDepth = getFrameCount(thread);
+    step->fromStackDepth = getThreadFrameCount(thread);
 
     if (step->fromStackDepth <= 0) {
         /*
          * If there are no stack frames, treat the step as though
          * from a native frame. This is most likely to occur at the

@@ -279,11 +273,11 @@
          */
         jint currentDepth;
         jint fromDepth;
         jint afterPopDepth;
 
-        currentDepth = getFrameCount(thread);
+        currentDepth = getThreadFrameCount(thread);
         fromDepth = step->fromStackDepth;
         afterPopDepth = currentDepth-1;
 
         LOG_STEP(("handleFramePopEvent: BEGIN fromDepth=%d, currentDepth=%d",
                         fromDepth, currentDepth));

@@ -339,19 +333,28 @@
              * The original stepping frame is about to be popped. Step
              * until we reach the next safe place to stop.
              */
             LOG_STEP(("handleFramePopEvent: starting singlestep, depth==OUT && fromDepth > afterPopDepth (%d>%d)",fromDepth, afterPopDepth));
             enableStepping(thread);
-        } else if (step->methodEnterHandlerNode != NULL &&
-                   fromDepth >= afterPopDepth) {
-            /*
-             * We installed a method entry event handler as part of a
-             * step into operation. We've popped back to the original
-             * stepping frame without finding a place to stop.
-             * Resume stepping in the original frame.
-             */
-            LOG_STEP(("handleFramePopEvent: starting singlestep, have methodEnter handler && depth==OUT && fromDepth >= afterPopDepth (%d>%d)",fromDepth, afterPopDepth));
+        } else if (step->methodEnterHandlerNode != NULL) {
+            /* We installed a method entry event handler as part of a step into operation. */
+            JDI_ASSERT(step->depth == JDWP_STEP_DEPTH(INTO));
+            if (fromDepth >= afterPopDepth) {
+                /*
+                 * We've popped back to the original stepping frame without finding a place to stop.
+                 * Resume stepping in the original frame.
+                 */
+                LOG_STEP(("handleFramePopEvent: starting singlestep, have methodEnter handler && depth==INTO && fromDepth >= afterPopDepth (%d>=%d)", fromDepth, afterPopDepth));
+            } else {
+                /*
+                 * The only way this should happen is if FramePop events were enabled to support a
+                 * CONTINUATION_RUN event while doing a STEP_INTO. See stepControl_handleContinuationRun().
+                 * Resume stepping in the current frame. If it is not the correct frame to resume stepping
+                 * in, then handleStep() will disable single stepping and setup another FramePop request.
+                 */
+                LOG_STEP(("handleFramePopEvent: starting singlestep, have methodEnter handler && depth==INTO && fromDepth < afterPopDepth (%d<%d)", fromDepth, afterPopDepth));
+            }
             enableStepping(thread);
             (void)eventHandler_free(step->methodEnterHandlerNode);
             step->methodEnterHandlerNode = NULL;
         }
         LOG_STEP(("handleFramePopEvent: finished"));

@@ -378,11 +381,11 @@
     if (step->pending) {
         /*
          *  Determine where we are on the call stack relative to where
          *  we started.
          */
-        jint currentDepth = getFrameCount(thread);
+        jint currentDepth = getThreadFrameCount(thread);
         jint fromDepth = step->fromStackDepth;
 
         LOG_STEP(("handleExceptionCatchEvent: fromDepth=%d, currentDepth=%d",
                         fromDepth, currentDepth));
 

@@ -425,62 +428,106 @@
     }
 
     stepControl_unlock();
 }
 
+/*
+ * Help deal with a METHOD_ENTRY event (or a simulated one). Returns JNI_TRUE if
+ * single stepping gets enabled.
+ */
+static jboolean
+methodEnterHelper(JNIEnv *env, jthread thread, StepRequest *step, jclass clazz, jmethodID method)
+{
+    jboolean steppingEnabled = JNI_FALSE;
+    if (step->pending) {
+        char *classname = getClassname(clazz);
+
+        /* This handler is relevant only to step into. */
+        JDI_ASSERT(step->depth == JDWP_STEP_DEPTH(INTO));
+
+        if ((!eventFilter_predictFiltering(step->stepHandlerNode, clazz, classname))
+            && (step->granularity != JDWP_STEP_SIZE(LINE) || hasLineNumbers(method))) {
+            /*
+             * We've found a suitable method in which to resume stepping.
+             * We can also get rid of the method entry handler now.
+             */
+            enableStepping(thread);
+            steppingEnabled = JNI_TRUE;
+            if ( step->methodEnterHandlerNode != NULL ) {
+                (void)eventHandler_free(step->methodEnterHandlerNode);
+                step->methodEnterHandlerNode = NULL;
+            }
+        }
+        jvmtiDeallocate(classname);
+        classname = NULL;
+    }
+    return steppingEnabled;
+}
+
 static void
 handleMethodEnterEvent(JNIEnv *env, EventInfo *evinfo,
                        HandlerNode *node,
                        struct bag *eventBag)
 {
     StepRequest *step;
-    jthread thread;
-
-    thread = evinfo->thread;
+    jthread thread = evinfo->thread;
 
     stepControl_lock();
-
+    LOG_STEP(("handleMethodEnterEvent: thread=%p", thread));
     step = threadControl_getStepRequest(thread);
     if (step == NULL) {
         EXIT_ERROR(AGENT_ERROR_INVALID_THREAD, "getting step request");
     }
+    methodEnterHelper(env, thread, step, evinfo->clazz, evinfo->method);
+    stepControl_unlock();
+}
 
-    if (step->pending) {
-        jclass    clazz;
+void
+stepControl_handleContinuationRun(JNIEnv *env, jthread thread, StepRequest *step)
+{
+    stepControl_lock();
+    if (step->methodEnterHandlerNode != NULL) {
+        /* 
+         * Looks like we were doing a single step INTO with filtering enabled, so a MethodEntry
+         * handler was installed to detect when we enter an unfiltered method. It's possible
+         * that the continuation method we are resuming execution in is unfiltered, but there
+         * will be no MethodEntry event for it. So we fake one here so the single stepping
+         * state is udpated appropriately.
+         */
+        jboolean steppingEnabled;
+        jclass clazz;
         jmethodID method;
-        char     *classname;
-
-        LOG_STEP(("handleMethodEnterEvent: thread=%p", thread));
-
-        clazz     = evinfo->clazz;
-        method    = evinfo->method;
-        classname = getClassname(clazz);
+        jlocation location;
+        jvmtiError error;
+        
+        JDI_ASSERT(step->depth == JDWP_STEP_DEPTH(INTO));
 
         /*
-         * This handler is relevant only to step into
+         * We leverage the existing MethodEntry handler support for this, but it needs to know
+         * the location of the MethodEntry, so we get this from the current frame.
          */
-        JDI_ASSERT(step->depth == JDWP_STEP_DEPTH(INTO));
+        error = getFrameLocation(thread, &clazz, &method, &location);
+        if (error != JVMTI_ERROR_NONE) {
+            EXIT_ERROR(error, "getting frame location");
+        }
+        steppingEnabled = methodEnterHelper(env, thread, step, clazz, method);
 
-        if (    (!eventFilter_predictFiltering(step->stepHandlerNode,
-                                               clazz, classname))
-             && (   step->granularity != JDWP_STEP_SIZE(LINE)
-                 || hasLineNumbers(method) ) ) {
+        if (!steppingEnabled) {
             /*
-             * We've found a suitable method in which to stop. Step
-             * until we reach the next safe location to complete the step->,
-             * and we can get rid of the method entry handler.
+             * It looks like the continuation is resuming in a filtered method. We need to setup
+             * a FramePop request on the current frame, so when it exits we can check if the 
+             * method we return to is filtered, and enable single stepping if not.
              */
-            enableStepping(thread);
-            if ( step->methodEnterHandlerNode != NULL ) {
-                (void)eventHandler_free(step->methodEnterHandlerNode);
-                step->methodEnterHandlerNode = NULL;
+            error = JVMTI_FUNC_PTR(gdata->jvmti,NotifyFramePop)
+                (gdata->jvmti, thread, 0);
+            if (error == JVMTI_ERROR_DUPLICATE) {
+                error = JVMTI_ERROR_NONE;
+            } else if (error != JVMTI_ERROR_NONE) {
+                EXIT_ERROR(error, "setting up notify frame pop");
             }
         }
-        jvmtiDeallocate(classname);
-        classname = NULL;
     }
-
     stepControl_unlock();
 }
 
 static void
 completeStep(JNIEnv *env, jthread thread, StepRequest *step)

@@ -507,11 +554,11 @@
         EXIT_ERROR(error, "initializing step state");
     }
 }
 
 jboolean
-stepControl_handleStep(JNIEnv *env, jthread thread,
+stepControl_handleStep(JNIEnv *env, jthread thread, jthread fiber, jboolean matchesFiber,
                        jclass clazz, jmethodID method)
 {
     jboolean completed = JNI_FALSE;
     StepRequest *step;
     jint currentDepth;

@@ -534,10 +581,13 @@
         goto done;
     }
 
     LOG_STEP(("stepControl_handleStep: thread=%p", thread));
 
+    /* Make sure the StepRequest is in agreement as to whether or not we are stepping in a fiber. */
+    JDI_ASSERT(step->is_fiber == matchesFiber);
+
     /*
      * We never filter step into instruction. It's always over on the
      * first step event.
      */
     if (step->depth == JDWP_STEP_DEPTH(INTO) &&

@@ -559,11 +609,11 @@
 
     /*
      *  Determine where we are on the call stack relative to where
      *  we started.
      */
-    currentDepth = getFrameCount(thread);
+    currentDepth = getThreadFrameCount(thread);
     fromDepth = step->fromStackDepth;
 
     if (fromDepth > currentDepth) {
         /*
          * We have returned from the caller. There are cases where

@@ -595,16 +645,17 @@
 
             if (step->depth == JDWP_STEP_DEPTH(INTO)) {
                 step->methodEnterHandlerNode =
                     eventHandler_createInternalThreadOnly(
                                        EI_METHOD_ENTRY,
-                                       handleMethodEnterEvent, thread);
+                                       handleMethodEnterEvent, matchesFiber ? fiber : thread);
                 if (step->methodEnterHandlerNode == NULL) {
                     EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE,
                                 "installing event method enter handler");
                 }
             }
+            LOG_STEP(("stepControl_handleStep: NotifyFramePop, (fromDepth currentDepth)(%d %d) ", fromDepth, currentDepth));
 
             error = JVMTI_FUNC_PTR(gdata->jvmti,NotifyFramePop)
                         (gdata->jvmti, thread, 0);
             if (error == JVMTI_ERROR_DUPLICATE) {
                 error = JVMTI_ERROR_NONE;

@@ -722,11 +773,11 @@
 
     stepControl_unlock();
 }
 
 static void
-initEvents(jthread thread, StepRequest *step)
+initEvents(jthread thread, jthread filter_thread, StepRequest *step)
 {
     /* Need to install frame pop handler and exception catch handler when
      * single-stepping is enabled (i.e. step-into or step-over/step-out
      * when fromStackDepth > 0).
      */

@@ -736,15 +787,16 @@
          * boost performance.
          */
         step->catchHandlerNode = eventHandler_createInternalThreadOnly(
                                      EI_EXCEPTION_CATCH,
                                      handleExceptionCatchEvent,
-                                     thread);
+                                     filter_thread);
+        JDI_ASSERT(step->framePopHandlerNode == NULL);
         step->framePopHandlerNode = eventHandler_createInternalThreadOnly(
                                         EI_FRAME_POP,
                                         handleFramePopEvent,
-                                        thread);
+                                        filter_thread);
 
         if (step->catchHandlerNode == NULL ||
             step->framePopHandlerNode == NULL) {
             EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE,
                         "installing step event handlers");

@@ -783,23 +835,56 @@
             JDI_ASSERT(JNI_FALSE);
     }
 }
 
 jvmtiError
-stepControl_beginStep(JNIEnv *env, jthread thread, jint size, jint depth,
+stepControl_beginStep(JNIEnv *env, jthread filter_thread,  jint size, jint depth,
                       HandlerNode *node)
 {
     StepRequest *step;
     jvmtiError error;
     jvmtiError error2;
+    jthread thread;
+    jboolean is_fiber;
+
+    /* filter_thread could be a fiber. Get the carrier thread it is mounted on. */
+    is_fiber = isFiber(filter_thread);
+    if (is_fiber) {
+        thread = getFiberThread(filter_thread);
+        if (thread == NULL) {
+            /* fiber fixme: It is possible for the StepRequest to have been made on an unmounted
+             * fiber, and currently we don't support this. 
+             */
+            LOG_STEP(("stepControl_beginStep: thread is an unmounted fiber(%p)", filter_thread));
+            return JVMTI_ERROR_INVALID_THREAD;
+        }
+    }  else {
+        thread = filter_thread;
+    }
 
-    LOG_STEP(("stepControl_beginStep: thread=%p,size=%d,depth=%d",
-                        thread, size, depth));
+    LOG_STEP(("stepControl_beginStep: filter_thread=%p,thread=%p,size=%d,depth=%d",
+              filter_thread, thread, size, depth));
 
     eventHandler_lock(); /* for proper lock order */
     stepControl_lock();
 
+    /* fiber fixme: we should consider getting the StepRequest from the fiber instead of the thread.
+     * That way we don't need to copy back and forth in threadControl_mountFiber and
+     * threadControl_unmountFiber. It would require some additional changes in the step event 
+     * support to always pass the fiber to threadControl_getStepRequest(). We also need to 
+     * deal with node->instructionStepMode, referenceing the fiber copy when appropriate. Note
+     * this will get tricky if you try to single step in both the fiber and thread. With the
+     * current impl, if you single step in the fiber first, hit a breakpoint while stepping over
+     * a method call, and then switch to the carrier thread and start to single step there,
+     * that will clear out the fiber single stepping. If we get the StepRequest
+     * from the fiber instead, it won't automatically clear out the fiber single stepping
+     * when you start single stepping in the carrier thread, but it won't work as expected either
+     * because JVMTI single stepping on the carrier thread will be disabled once the single
+     * step is complete. We'd need to detect that we were single stepping on the fiber and
+     * keep JVMTI single stepping enabled, or we need to clear the single stepping state of
+     * the fiber.
+     */
     step = threadControl_getStepRequest(thread);
     if (step == NULL) {
         error = AGENT_ERROR_INVALID_THREAD;
         /* Normally not getting a StepRequest struct pointer is a fatal error
          *   but on a beginStep, we just return an error code.

@@ -813,17 +898,18 @@
             /*
              * Overwrite any currently executing step.
              */
             step->granularity = size;
             step->depth = depth;
+            step->is_fiber = is_fiber;
             step->catchHandlerNode = NULL;
             step->framePopHandlerNode = NULL;
             step->methodEnterHandlerNode = NULL;
             step->stepHandlerNode = node;
             error = initState(env, thread, step);
             if (error == JVMTI_ERROR_NONE) {
-                initEvents(thread, step);
+                initEvents(thread, filter_thread, step);
             }
             /* false means it is not okay to unblock the commandLoop thread */
             error2 = threadControl_resumeThread(thread, JNI_FALSE);
             if (error2 != JVMTI_ERROR_NONE && error == JVMTI_ERROR_NONE) {
                 error = error2;

@@ -885,10 +971,17 @@
     LOG_STEP(("stepControl_endStep: thread=%p", thread));
 
     eventHandler_lock(); /* for proper lock order */
     stepControl_lock();
 
+    if (isFiber(thread)) {
+        jthread carrier_thread = getFiberThread(thread);
+        /* During termination the fiber might not be mounted, so a NULL carrier thead is ok in that case. */
+        if (carrier_thread != NULL) {
+            thread = carrier_thread;
+        }
+    }
     step = threadControl_getStepRequest(thread);
     if (step != NULL) {
         clearStep(thread, step);
         error = JVMTI_ERROR_NONE;
     } else {
< prev index next >