/* * 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); }