fe008ae27a
Reviewed-by: darcy, weijun
927 lines
30 KiB
C
927 lines
30 KiB
C
/*
|
|
* 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);
|
|
}
|