C++程序  |  927行  |  30.25 KB

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