| /* | |
| * Copyright (c) 1998, 2005, 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 | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| #include "util.h" | |
| #include "stepControl.h" | |
| #include "eventHandler.h" | |
| #include "eventHelper.h" | |
| #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 | |
| * complete. While a step request is pending, we may need to temporarily | |
| * disable and re-enable stepping, but we can't just remove the handlers | |
| * because that would break the application's ability to remove the | |
| * events. So, for step events only, we directly enable and disable stepping. | |
| * This is safe because there can only ever be one pending step request | |
| * per thread. | |
| */ | |
| static void | |
| enableStepping(jthread thread) | |
| { | |
| jvmtiError error; | |
| LOG_STEP(("enableStepping: thread=%p", thread)); | |
| error = threadControl_setEventMode(JVMTI_ENABLE, EI_SINGLE_STEP, | |
| thread); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "enabling single step"); | |
| } | |
| } | |
| static void | |
| disableStepping(jthread thread) | |
| { | |
| jvmtiError error; | |
| LOG_STEP(("disableStepping: thread=%p", thread)); | |
| error = threadControl_setEventMode(JVMTI_DISABLE, EI_SINGLE_STEP, | |
| thread); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "disabling single step"); | |
| } | |
| } | |
| static jvmtiError | |
| getFrameLocation(jthread thread, | |
| jclass *pclazz, jmethodID *pmethod, jlocation *plocation) | |
| { | |
| jvmtiError error; | |
| *pclazz = NULL; | |
| *pmethod = NULL; | |
| *plocation = -1; | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,GetFrameLocation) | |
| (gdata->jvmti, thread, 0, pmethod, plocation); | |
| if (error == JVMTI_ERROR_NONE && *pmethod!=NULL ) { | |
| /* This also serves to verify that the methodID is valid */ | |
| error = methodClass(*pmethod, pclazz); | |
| } | |
| return error; | |
| } | |
| static void | |
| getLineNumberTable(jmethodID method, jint *pcount, | |
| jvmtiLineNumberEntry **ptable) | |
| { | |
| jvmtiError error; | |
| *pcount = 0; | |
| *ptable = NULL; | |
| /* If the method is native or obsolete, don't even ask for the line table */ | |
| if ( isMethodObsolete(method) || isMethodNative(method)) { | |
| return; | |
| } | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,GetLineNumberTable) | |
| (gdata->jvmti, method, pcount, ptable); | |
| if (error != JVMTI_ERROR_NONE) { | |
| *pcount = 0; | |
| } | |
| } | |
| static jint | |
| findLineNumber(jthread thread, jlocation location, | |
| jvmtiLineNumberEntry *lines, jint count) | |
| { | |
| jint line = -1; | |
| if (location != -1) { | |
| if (count > 0) { | |
| jint i; | |
| /* any preface before first line is assigned to first line */ | |
| for (i=1; i<count; i++) { | |
| if (location < lines[i].start_location) { | |
| break; | |
| } | |
| } | |
| line = lines[i-1].line_number; | |
| } | |
| } | |
| return line; | |
| } | |
| static jboolean | |
| hasLineNumbers(jmethodID method) | |
| { | |
| jint count; | |
| jvmtiLineNumberEntry *table; | |
| getLineNumberTable(method, &count, &table); | |
| if ( count == 0 ) { | |
| return JNI_FALSE; | |
| } else { | |
| jvmtiDeallocate(table); | |
| } | |
| return JNI_TRUE; | |
| } | |
| static jvmtiError | |
| initState(JNIEnv *env, jthread thread, StepRequest *step) | |
| { | |
| jvmtiError error; | |
| /* | |
| * Initial values that may be changed below | |
| */ | |
| step->fromLine = -1; | |
| step->fromNative = JNI_FALSE; | |
| step->frameExited = JNI_FALSE; | |
| step->fromStackDepth = getFrameCount(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 | |
| * beginning of a debug session, right after the VM_INIT event, | |
| * so we need to do something intelligent. | |
| */ | |
| step->fromNative = JNI_TRUE; | |
| return JVMTI_ERROR_NONE; | |
| } | |
| /* | |
| * Try to get a notification on frame pop. If we're in an opaque frame | |
| * we won't be able to, but we can use other methods to detect that | |
| * a native frame has exited. | |
| * | |
| * TO DO: explain the need for this notification. | |
| */ | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,NotifyFramePop) | |
| (gdata->jvmti, thread, 0); | |
| if (error == JVMTI_ERROR_OPAQUE_FRAME) { | |
| step->fromNative = JNI_TRUE; | |
| error = JVMTI_ERROR_NONE; | |
| /* continue without error */ | |
| } else if (error == JVMTI_ERROR_DUPLICATE) { | |
| error = JVMTI_ERROR_NONE; | |
| /* Already being notified, continue without error */ | |
| } else if (error != JVMTI_ERROR_NONE) { | |
| return error; | |
| } | |
| LOG_STEP(("initState(): frame=%d", step->fromStackDepth)); | |
| /* | |
| * Note: we can't undo the frame pop notify, so | |
| * we'll just have to let the handler ignore it if | |
| * there are any errors below. | |
| */ | |
| if (step->granularity == JDWP_STEP_SIZE(LINE) ) { | |
| LOG_STEP(("initState(): Begin line step")); | |
| WITH_LOCAL_REFS(env, 1) { | |
| jclass clazz; | |
| jmethodID method; | |
| jlocation location; | |
| error = getFrameLocation(thread, &clazz, &method, &location); | |
| if (error == JVMTI_ERROR_NONE) { | |
| /* Clear out previous line table only if we changed methods */ | |
| if ( method != step->method ) { | |
| step->lineEntryCount = 0; | |
| if (step->lineEntries != NULL) { | |
| jvmtiDeallocate(step->lineEntries); | |
| step->lineEntries = NULL; | |
| } | |
| step->method = method; | |
| getLineNumberTable(step->method, | |
| &step->lineEntryCount, &step->lineEntries); | |
| if (step->lineEntryCount > 0) { | |
| convertLineNumberTable(env, clazz, | |
| &step->lineEntryCount, &step->lineEntries); | |
| } | |
| } | |
| step->fromLine = findLineNumber(thread, location, | |
| step->lineEntries, step->lineEntryCount); | |
| } | |
| } END_WITH_LOCAL_REFS(env); | |
| } | |
| return error; | |
| } | |
| /* | |
| * TO DO: The step handlers (handleFrameChange and handleStep can | |
| * be broken down and made simpler now that we can install and de-install event | |
| * handlers. | |
| */ | |
| static void | |
| handleFramePopEvent(JNIEnv *env, EventInfo *evinfo, | |
| HandlerNode *node, | |
| struct bag *eventBag) | |
| { | |
| StepRequest *step; | |
| jthread thread = evinfo->thread; | |
| stepControl_lock(); | |
| step = threadControl_getStepRequest(thread); | |
| if (step == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_THREAD, "getting step request"); | |
| } | |
| if (step->pending) { | |
| /* | |
| * Note: current depth is reported as *before* the pending frame | |
| * pop. | |
| */ | |
| jint currentDepth; | |
| jint fromDepth; | |
| jint afterPopDepth; | |
| currentDepth = getFrameCount(thread); | |
| fromDepth = step->fromStackDepth; | |
| afterPopDepth = currentDepth-1; | |
| LOG_STEP(("handleFramePopEvent: BEGIN fromDepth=%d, currentDepth=%d", | |
| fromDepth, currentDepth)); | |
| /* | |
| * If we are exiting the original stepping frame, record that | |
| * fact here. Once the next step event comes in, we can safely | |
| * stop stepping there. | |
| */ | |
| if (fromDepth > afterPopDepth ) { | |
| step->frameExited = JNI_TRUE; | |
| } | |
| if (step->depth == JDWP_STEP_DEPTH(OVER)) { | |
| /* | |
| * Either | |
| * 1) the original stepping frame is about to be popped | |
| * [fromDepth == currentDepth]. Re-enable stepping to | |
| * reach a point where we can stop. | |
| * 2) a method called from the stepping frame has returned | |
| * (during which we had stepping disabled) | |
| * [fromDepth == currentDepth - 1]. Re-enable stepping | |
| * so that we can continue instructions steps in the | |
| * original stepping frame. | |
| * 3) a method further down the call chain has notified | |
| * of a frame pop [fromDepth < currentDepth - 1]. This | |
| * *might* represent case (2) above if the stepping frame | |
| * was calling a native method which in turn called a | |
| * java method. If so, we must enable stepping to | |
| * ensure that we get control back after the intervening | |
| * native frame is popped (you can't get frame pop | |
| * notifications on native frames). If the native caller | |
| * calls another Java method before returning, | |
| * stepping will be diabled again and another frame pop | |
| * will be awaited. | |
| * | |
| * If it turns out that this is not case (2) with native | |
| * methods, then the enabled stepping is benign and | |
| * will be disabled again on the next step event. | |
| * | |
| * Note that the condition not covered above, | |
| * [fromDepth > currentDepth] shouldn't happen since it means | |
| * that too many frames have been popped. For robustness, | |
| * we enable stepping in that case too, so that the errant | |
| * step-over can be stopped. | |
| * | |
| */ | |
| LOG_STEP(("handleFramePopEvent: starting singlestep, depth==OVER")); | |
| enableStepping(thread); | |
| } else if (step->depth == JDWP_STEP_DEPTH(OUT) && | |
| fromDepth > afterPopDepth) { | |
| /* | |
| * 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)); | |
| enableStepping(thread); | |
| (void)eventHandler_free(step->methodEnterHandlerNode); | |
| step->methodEnterHandlerNode = NULL; | |
| } | |
| LOG_STEP(("handleFramePopEvent: finished")); | |
| } | |
| stepControl_unlock(); | |
| } | |
| static void | |
| handleExceptionCatchEvent(JNIEnv *env, EventInfo *evinfo, | |
| HandlerNode *node, | |
| struct bag *eventBag) | |
| { | |
| StepRequest *step; | |
| jthread thread = evinfo->thread; | |
| stepControl_lock(); | |
| step = threadControl_getStepRequest(thread); | |
| if (step == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_THREAD, "getting step request"); | |
| } | |
| if (step->pending) { | |
| /* | |
| * Determine where we are on the call stack relative to where | |
| * we started. | |
| */ | |
| jint currentDepth = getFrameCount(thread); | |
| jint fromDepth = step->fromStackDepth; | |
| LOG_STEP(("handleExceptionCatchEvent: fromDepth=%d, currentDepth=%d", | |
| fromDepth, currentDepth)); | |
| /* | |
| * If we are exiting the original stepping frame, record that | |
| * fact here. Once the next step event comes in, we can safely | |
| * stop stepping there. | |
| */ | |
| if (fromDepth > currentDepth) { | |
| step->frameExited = JNI_TRUE; | |
| } | |
| if (step->depth == JDWP_STEP_DEPTH(OVER) && | |
| fromDepth >= currentDepth) { | |
| /* | |
| * Either the original stepping frame is done, | |
| * or a called method has returned (during which we had stepping | |
| * disabled). In either case we must resume stepping. | |
| */ | |
| enableStepping(thread); | |
| } else if (step->depth == JDWP_STEP_DEPTH(OUT) && | |
| fromDepth > currentDepth) { | |
| /* | |
| * The original stepping frame is done. Step | |
| * until we reach the next safe place to stop. | |
| */ | |
| enableStepping(thread); | |
| } else if (step->methodEnterHandlerNode != NULL && | |
| fromDepth >= currentDepth) { | |
| /* | |
| * We installed a method entry event handler as part of a | |
| * step into operation. We've popped back to the original | |
| * stepping frame or higher without finding a place to stop. | |
| * Resume stepping in the original frame. | |
| */ | |
| enableStepping(thread); | |
| (void)eventHandler_free(step->methodEnterHandlerNode); | |
| step->methodEnterHandlerNode = NULL; | |
| } | |
| } | |
| stepControl_unlock(); | |
| } | |
| static void | |
| handleMethodEnterEvent(JNIEnv *env, EventInfo *evinfo, | |
| HandlerNode *node, | |
| struct bag *eventBag) | |
| { | |
| StepRequest *step; | |
| jthread thread; | |
| thread = evinfo->thread; | |
| stepControl_lock(); | |
| step = threadControl_getStepRequest(thread); | |
| if (step == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_THREAD, "getting step request"); | |
| } | |
| if (step->pending) { | |
| jclass clazz; | |
| jmethodID method; | |
| char *classname; | |
| LOG_STEP(("handleMethodEnterEvent: thread=%p", thread)); | |
| clazz = evinfo->clazz; | |
| method = evinfo->method; | |
| 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 stop. Step | |
| * until we reach the next safe location to complete the step->, | |
| * and we can get rid of the method entry handler. | |
| */ | |
| enableStepping(thread); | |
| if ( step->methodEnterHandlerNode != NULL ) { | |
| (void)eventHandler_free(step->methodEnterHandlerNode); | |
| step->methodEnterHandlerNode = NULL; | |
| } | |
| } | |
| jvmtiDeallocate(classname); | |
| classname = NULL; | |
| } | |
| stepControl_unlock(); | |
| } | |
| static void | |
| completeStep(JNIEnv *env, jthread thread, StepRequest *step) | |
| { | |
| jvmtiError error; | |
| /* | |
| * We've completed a step; reset state for the next one, if any | |
| */ | |
| LOG_STEP(("completeStep: thread=%p", thread)); | |
| if (step->methodEnterHandlerNode != NULL) { | |
| (void)eventHandler_free(step->methodEnterHandlerNode); | |
| step->methodEnterHandlerNode = NULL; | |
| } | |
| error = initState(env, thread, step); | |
| if (error != JVMTI_ERROR_NONE) { | |
| /* | |
| * None of the initState errors should happen after one step | |
| * has successfully completed. | |
| */ | |
| EXIT_ERROR(error, "initializing step state"); | |
| } | |
| } | |
| jboolean | |
| stepControl_handleStep(JNIEnv *env, jthread thread, | |
| jclass clazz, jmethodID method) | |
| { | |
| jboolean completed = JNI_FALSE; | |
| StepRequest *step; | |
| jint currentDepth; | |
| jint fromDepth; | |
| jvmtiError error; | |
| char *classname; | |
| classname = NULL; | |
| stepControl_lock(); | |
| step = threadControl_getStepRequest(thread); | |
| if (step == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_THREAD, "getting step request"); | |
| } | |
| /* | |
| * If no step is currently pending, ignore the event | |
| */ | |
| if (!step->pending) { | |
| goto done; | |
| } | |
| LOG_STEP(("stepControl_handleStep: thread=%p", thread)); | |
| /* | |
| * We never filter step into instruction. It's always over on the | |
| * first step event. | |
| */ | |
| if (step->depth == JDWP_STEP_DEPTH(INTO) && | |
| step->granularity == JDWP_STEP_SIZE(MIN)) { | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, into min")); | |
| goto done; | |
| } | |
| /* | |
| * If we have left the method in which | |
| * stepping started, the step is always complete. | |
| */ | |
| if (step->frameExited) { | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, frame exited")); | |
| goto done; | |
| } | |
| /* | |
| * Determine where we are on the call stack relative to where | |
| * we started. | |
| */ | |
| currentDepth = getFrameCount(thread); | |
| fromDepth = step->fromStackDepth; | |
| if (fromDepth > currentDepth) { | |
| /* | |
| * We have returned from the caller. There are cases where | |
| * we don't get frame pop notifications | |
| * (e.g. stepping from opaque frames), and that's when | |
| * this code will be reached. Complete the step-> | |
| */ | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, fromDepth>currentDepth(%d>%d)", fromDepth, currentDepth)); | |
| } else if (fromDepth < currentDepth) { | |
| /* We have dropped into a called method. */ | |
| if ( step->depth == JDWP_STEP_DEPTH(INTO) | |
| && (!eventFilter_predictFiltering(step->stepHandlerNode, clazz, | |
| (classname = getClassname(clazz)))) | |
| && hasLineNumbers(method) ) { | |
| /* Stepped into a method with lines, so we're done */ | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, fromDepth<currentDepth(%d<%d) and into method with lines", fromDepth, currentDepth)); | |
| } else { | |
| /* | |
| * We need to continue, but don't want the overhead of step | |
| * events from this method. So, we disable stepping and | |
| * enable a frame pop. If we're stepping into, we also | |
| * enable method enter events because a called frame may be | |
| * where we want to stop. | |
| */ | |
| disableStepping(thread); | |
| if (step->depth == JDWP_STEP_DEPTH(INTO)) { | |
| step->methodEnterHandlerNode = | |
| eventHandler_createInternalThreadOnly( | |
| EI_METHOD_ENTRY, | |
| handleMethodEnterEvent, thread); | |
| if (step->methodEnterHandlerNode == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE, | |
| "installing event method enter handler"); | |
| } | |
| } | |
| 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; | |
| } else { | |
| /* | |
| * We are at the same stack depth where stepping started. | |
| * Instruction steps are complete at this point. For line | |
| * steps we must check to see whether we've moved to a | |
| * different line. | |
| */ | |
| if (step->granularity == JDWP_STEP_SIZE(MIN)) { | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, fromDepth==currentDepth(%d) and min", fromDepth)); | |
| } else { | |
| if (step->fromLine != -1) { | |
| jint line = -1; | |
| jlocation location; | |
| jmethodID method; | |
| WITH_LOCAL_REFS(env, 1) { | |
| jclass clazz; | |
| error = getFrameLocation(thread, | |
| &clazz, &method, &location); | |
| if ( isMethodObsolete(method)) { | |
| method = NULL; | |
| location = -1; | |
| } | |
| if (error != JVMTI_ERROR_NONE || location == -1) { | |
| EXIT_ERROR(error, "getting frame location"); | |
| } | |
| if ( method == step->method ) { | |
| LOG_STEP(("stepControl_handleStep: checking line location")); | |
| log_debugee_location("stepControl_handleStep: checking line loc", | |
| thread, method, location); | |
| line = findLineNumber(thread, location, | |
| step->lineEntries, step->lineEntryCount); | |
| } | |
| if (line != step->fromLine) { | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, fromDepth==currentDepth(%d) and different line", fromDepth)); | |
| } | |
| } END_WITH_LOCAL_REFS(env); | |
| } else { | |
| /* | |
| * This is a rare case. We have stepped from a location | |
| * inside a native method to a location within a Java | |
| * method at the same stack depth. This means that | |
| * the original native method returned to another | |
| * native method which, in turn, invoked a Java method. | |
| * | |
| * Since the original frame was native, we were unable | |
| * to ask for a frame pop event, and, thus, could not | |
| * set the step->frameExited flag when the original | |
| * method was done. Instead we end up here | |
| * and act just as though the frameExited flag was set | |
| * and complete the step immediately. | |
| */ | |
| completed = JNI_TRUE; | |
| LOG_STEP(("stepControl_handleStep: completed, fromDepth==currentDepth(%d) and no line", fromDepth)); | |
| } | |
| } | |
| LOG_STEP(("stepControl_handleStep: finished")); | |
| } | |
| done: | |
| if (completed) { | |
| completeStep(env, thread, step); | |
| } | |
| stepControl_unlock(); | |
| return completed; | |
| } | |
| void | |
| stepControl_initialize(void) | |
| { | |
| stepLock = debugMonitorCreate("JDWP Step Handler Lock"); | |
| } | |
| void | |
| stepControl_reset(void) | |
| { | |
| } | |
| /* | |
| * Reset step control request stack depth and line number. | |
| */ | |
| void | |
| stepControl_resetRequest(jthread thread) | |
| { | |
| StepRequest *step; | |
| jvmtiError error; | |
| LOG_STEP(("stepControl_resetRequest: thread=%p", thread)); | |
| stepControl_lock(); | |
| step = threadControl_getStepRequest(thread); | |
| if (step != NULL) { | |
| JNIEnv *env; | |
| env = getEnv(); | |
| error = initState(env, thread, step); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "initializing step state"); | |
| } | |
| } else { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_THREAD, "getting step request"); | |
| } | |
| stepControl_unlock(); | |
| } | |
| static void | |
| initEvents(jthread 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). | |
| */ | |
| if (step->depth == JDWP_STEP_DEPTH(INTO) || step->fromStackDepth > 0) { | |
| /* | |
| * TO DO: These might be able to applied more selectively to | |
| * boost performance. | |
| */ | |
| step->catchHandlerNode = eventHandler_createInternalThreadOnly( | |
| EI_EXCEPTION_CATCH, | |
| handleExceptionCatchEvent, | |
| thread); | |
| step->framePopHandlerNode = eventHandler_createInternalThreadOnly( | |
| EI_FRAME_POP, | |
| handleFramePopEvent, | |
| thread); | |
| if (step->catchHandlerNode == NULL || | |
| step->framePopHandlerNode == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE, | |
| "installing step event handlers"); | |
| } | |
| } | |
| /* | |
| * Initially enable stepping: | |
| * 1) For step into, always | |
| * 2) For step over, unless right after the VM_INIT. | |
| * Enable stepping for STEP_MIN or STEP_LINE with or without line numbers. | |
| * If the class is redefined then non EMCP methods may not have line | |
| * number info. So enable line stepping for non line number so that it | |
| * behaves like STEP_MIN/STEP_OVER. | |
| * 3) For step out, only if stepping from native, except right after VM_INIT | |
| * | |
| * (right after VM_INIT, a step->over or out is identical to running | |
| * forever) | |
| */ | |
| switch (step->depth) { | |
| case JDWP_STEP_DEPTH(INTO): | |
| enableStepping(thread); | |
| break; | |
| case JDWP_STEP_DEPTH(OVER): | |
| if (step->fromStackDepth > 0 && !step->fromNative ) { | |
| enableStepping(thread); | |
| } | |
| break; | |
| case JDWP_STEP_DEPTH(OUT): | |
| if (step->fromNative && | |
| (step->fromStackDepth > 0)) { | |
| enableStepping(thread); | |
| } | |
| break; | |
| default: | |
| JDI_ASSERT(JNI_FALSE); | |
| } | |
| } | |
| jvmtiError | |
| stepControl_beginStep(JNIEnv *env, jthread thread, jint size, jint depth, | |
| HandlerNode *node) | |
| { | |
| StepRequest *step; | |
| jvmtiError error; | |
| jvmtiError error2; | |
| LOG_STEP(("stepControl_beginStep: thread=%p,size=%d,depth=%d", | |
| thread, size, depth)); | |
| eventHandler_lock(); /* for proper lock order */ | |
| stepControl_lock(); | |
| 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. | |
| */ | |
| } else { | |
| /* | |
| * In case the thread isn't already suspended, do it again. | |
| */ | |
| error = threadControl_suspendThread(thread, JNI_FALSE); | |
| if (error == JVMTI_ERROR_NONE) { | |
| /* | |
| * Overwrite any currently executing step. | |
| */ | |
| step->granularity = size; | |
| step->depth = depth; | |
| 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); | |
| } | |
| /* 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; | |
| } | |
| /* | |
| * If everything went ok, indicate a step is pending. | |
| */ | |
| if (error == JVMTI_ERROR_NONE) { | |
| step->pending = JNI_TRUE; | |
| } | |
| } else { | |
| EXIT_ERROR(error, "stepControl_beginStep: cannot suspend thread"); | |
| } | |
| } | |
| stepControl_unlock(); | |
| eventHandler_unlock(); | |
| return error; | |
| } | |
| static void | |
| clearStep(jthread thread, StepRequest *step) | |
| { | |
| if (step->pending) { | |
| disableStepping(thread); | |
| if ( step->catchHandlerNode != NULL ) { | |
| (void)eventHandler_free(step->catchHandlerNode); | |
| step->catchHandlerNode = NULL; | |
| } | |
| if ( step->framePopHandlerNode!= NULL ) { | |
| (void)eventHandler_free(step->framePopHandlerNode); | |
| step->framePopHandlerNode = NULL; | |
| } | |
| if ( step->methodEnterHandlerNode != NULL ) { | |
| (void)eventHandler_free(step->methodEnterHandlerNode); | |
| step->methodEnterHandlerNode = NULL; | |
| } | |
| step->pending = JNI_FALSE; | |
| /* | |
| * Warning: Do not clear step->method, step->lineEntryCount, | |
| * or step->lineEntries here, they will likely | |
| * be needed on the next step. | |
| */ | |
| } | |
| } | |
| jvmtiError | |
| stepControl_endStep(jthread thread) | |
| { | |
| StepRequest *step; | |
| jvmtiError error; | |
| LOG_STEP(("stepControl_endStep: thread=%p", thread)); | |
| eventHandler_lock(); /* for proper lock order */ | |
| stepControl_lock(); | |
| step = threadControl_getStepRequest(thread); | |
| if (step != NULL) { | |
| clearStep(thread, step); | |
| error = JVMTI_ERROR_NONE; | |
| } else { | |
| /* If the stepRequest can't be gotten, then this thread no longer | |
| * exists, just return, don't die here, this is normal at | |
| * termination time. Return JVMTI_ERROR_NONE so the thread Ref | |
| * can be tossed. | |
| */ | |
| error = JVMTI_ERROR_NONE; | |
| } | |
| stepControl_unlock(); | |
| eventHandler_unlock(); | |
| return error; | |
| } | |
| void | |
| stepControl_clearRequest(jthread thread, StepRequest *step) | |
| { | |
| LOG_STEP(("stepControl_clearRequest: thread=%p", thread)); | |
| clearStep(thread, step); | |
| } | |
| void | |
| stepControl_lock(void) | |
| { | |
| debugMonitorEnter(stepLock); | |
| } | |
| void | |
| stepControl_unlock(void) | |
| { | |
| debugMonitorExit(stepLock); | |
| } |