8296089: Remove debug agent code for special handling of Thread.resume()

Reviewed-by: alanb
This commit is contained in:
Chris Plummer 2022-11-14 20:22:45 +00:00
parent c71d87e54c
commit 2f7dc5c4cd
3 changed files with 22 additions and 363 deletions
src/jdk.jdwp.agent/share/native/libjdwp

@ -58,10 +58,6 @@ typedef struct CoLocatedEventInfo_ {
* suspend counts. It also acts as a repository for other
* per-thread state such as the current method invocation or
* current step.
*
* suspendCount is the number of outstanding suspends
* from the debugger. suspends from the app itself are
* not included in this count.
*/
typedef struct ThreadNode {
jthread thread;
@ -74,11 +70,9 @@ typedef struct ThreadNode {
unsigned int popFrameEvent : 1;
unsigned int popFrameProceed : 1;
unsigned int popFrameThread : 1;
unsigned int handlingAppResume : 1;
EventIndex current_ei; /* Used to determine if we are currently handling an event on this thread. */
jobject pendingStop; /* Object we are throwing to stop the thread (ThreadReferenceImpl.stop). */
jint suspendCount;
jint resumeFrameDepth; /* !=0 => This thread is in a call to Thread.resume() */
jint suspendCount; /* Number of outstanding suspends from the debugger. */
jvmtiEventMode instructionStepMode;
StepRequest currentStep;
InvokeRequest currentInvoke;
@ -112,10 +106,6 @@ static jrawMonitorID popFrameEventLock = NULL;
static jrawMonitorID popFrameProceedLock = NULL;
static jrawMonitorID threadLock;
static jlocation resumeLocation;
static HandlerNode *breakpointHandlerNode;
static HandlerNode *framePopHandlerNode;
static HandlerNode *catchHandlerNode;
static jvmtiError threadControl_removeDebugThread(jthread thread);
@ -152,20 +142,6 @@ static void dumpThreadList(ThreadList *list);
static void dumpThread(ThreadNode *node);
#endif
static jint
getStackDepth(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;
}
/* Get the state of the thread direct from JVMTI */
static jvmtiError
threadState(jthread thread, jint *pstate)
@ -684,240 +660,22 @@ releaseLocks(void)
void
threadControl_initialize(void)
{
jlocation unused;
jvmtiError error;
suspendAllCount = 0;
runningThreads.first = NULL;
otherThreads.first = NULL;
runningVThreads.first = NULL;
debugThreadCount = 0;
threadLock = debugMonitorCreate("JDWP Thread Lock");
if (gdata->threadClass==NULL) {
EXIT_ERROR(AGENT_ERROR_NULL_POINTER, "no java.lang.thread class");
}
if (gdata->threadResume==0) {
EXIT_ERROR(AGENT_ERROR_NULL_POINTER, "cannot resume thread");
}
/* Get the java.lang.Thread.resume() method beginning location */
error = methodLocation(gdata->threadResume, &resumeLocation, &unused);
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error, "getting method location");
}
}
static jthread
getResumee(jthread resumingThread)
{
jthread resumee = NULL;
jvmtiError error;
jobject object;
FrameNumber fnum = 0;
error = JVMTI_FUNC_PTR(gdata->jvmti,GetLocalObject)
(gdata->jvmti, resumingThread, fnum, 0, &object);
if (error == JVMTI_ERROR_NONE) {
resumee = object;
}
return resumee;
}
static jboolean
pendingAppResume(jboolean includeSuspended)
{
ThreadList *list;
ThreadNode *node;
list = &runningThreads;
node = list->first;
while (node != NULL) {
if (node->resumeFrameDepth > 0) {
if (includeSuspended) {
return JNI_TRUE;
} else {
jvmtiError error;
jint state;
error = threadState(node->thread, &state);
if (error != JVMTI_ERROR_NONE) {
EXIT_ERROR(error, "getting thread state");
}
/* !node->handlingAppResume && resumeFrameDepth > 0
* means the thread has entered Thread.resume() */
if (!(state & JVMTI_THREAD_STATE_SUSPENDED) &&
!node->handlingAppResume) {
return JNI_TRUE;
}
}
}
node = node->next;
if (node == NULL && list == &runningThreads) {
// We need to look at runningVThreads after we are done with runningThreads.
list = &runningVThreads;
node = list->first;
}
}
return JNI_FALSE;
}
static void
notifyAppResumeComplete(void)
{
debugMonitorNotifyAll(threadLock);
if (!pendingAppResume(JNI_TRUE)) {
if (framePopHandlerNode != NULL) {
(void)eventHandler_free(framePopHandlerNode);
framePopHandlerNode = NULL;
}
if (catchHandlerNode != NULL) {
(void)eventHandler_free(catchHandlerNode);
catchHandlerNode = NULL;
}
}
}
/*
* Event handler for FRAME_POP and EXCEPTION_CATCH when in Thread.resume()
* so we can detect its completion.
*/
static void
handleAppResumeCompletion(JNIEnv *env, EventInfo *evinfo,
HandlerNode *handlerNode,
struct bag *eventBag)
{
ThreadNode *node;
jthread thread;
thread = evinfo->thread;
debugMonitorEnter(threadLock);
node = findRunningThread(thread);
if (node != NULL) {
if (node->resumeFrameDepth > 0) {
jint compareDepth = getStackDepth(thread);
if (evinfo->ei == EI_FRAME_POP) {
compareDepth--;
}
if (compareDepth < node->resumeFrameDepth) {
node->resumeFrameDepth = 0;
notifyAppResumeComplete();
}
}
}
debugMonitorExit(threadLock);
}
static void
blockOnDebuggerSuspend(jthread thread)
{
ThreadNode *node;
node = findThread(NULL, thread);
if (node != NULL) {
while (node && node->suspendCount > 0) {
debugMonitorWait(threadLock);
node = findThread(NULL, thread);
}
}
}
/*
* The caller is expected to hold threadLock and handlerLock.
* eventHandler_createInternalThreadOnly() can deadlock because of
* wrong lock ordering if the caller does not hold handlerLock.
*/
static void
trackAppResume(jthread thread)
{
jvmtiError error;
FrameNumber fnum;
ThreadNode *node;
fnum = 0;
node = findRunningThread(thread);
if (node != NULL) {
JDI_ASSERT(node->resumeFrameDepth == 0);
error = JVMTI_FUNC_PTR(gdata->jvmti,NotifyFramePop)
(gdata->jvmti, thread, fnum);
if (error == JVMTI_ERROR_NONE) {
jint frameDepth = getStackDepth(thread);
if ((frameDepth > 0) && (framePopHandlerNode == NULL)) {
framePopHandlerNode = eventHandler_createInternalThreadOnly(
EI_FRAME_POP,
handleAppResumeCompletion,
thread);
catchHandlerNode = eventHandler_createInternalThreadOnly(
EI_EXCEPTION_CATCH,
handleAppResumeCompletion,
thread);
if ((framePopHandlerNode == NULL) ||
(catchHandlerNode == NULL)) {
(void)eventHandler_free(framePopHandlerNode);
framePopHandlerNode = NULL;
(void)eventHandler_free(catchHandlerNode);
catchHandlerNode = NULL;
}
}
if ((framePopHandlerNode != NULL) &&
(catchHandlerNode != NULL) &&
(frameDepth > 0)) {
node->resumeFrameDepth = frameDepth;
}
}
}
}
/* Global breakpoint handler for Thread.resume() */
static void
handleAppResumeBreakpoint(JNIEnv *env, EventInfo *evinfo,
HandlerNode *handlerNode,
struct bag *eventBag)
{
jthread resumer = evinfo->thread;
debugMonitorEnter(threadLock);
/*
* Actual handling has to be deferred. We cannot block right here if the
* target of the resume call is suspended by the debugger since we are
* holding handlerLock which must not be released. See doPendingTasks().
*/
if (resumer != NULL) {
ThreadNode* node = findThread(&runningThreads, resumer);
if (node != NULL) {
node->handlingAppResume = JNI_TRUE;
}
}
debugMonitorExit(threadLock);
}
void
threadControl_onConnect(void)
{
breakpointHandlerNode = eventHandler_createInternalBreakpoint(
handleAppResumeBreakpoint, NULL,
gdata->threadClass, gdata->threadResume, resumeLocation);
}
void
threadControl_onDisconnect(void)
{
if (breakpointHandlerNode != NULL) {
(void)eventHandler_free(breakpointHandlerNode);
breakpointHandlerNode = NULL;
}
if (framePopHandlerNode != NULL) {
(void)eventHandler_free(framePopHandlerNode);
framePopHandlerNode = NULL;
}
if (catchHandlerNode != NULL) {
(void)eventHandler_free(catchHandlerNode);
catchHandlerNode = NULL;
}
}
void
@ -991,14 +749,12 @@ commonSuspendByNode(ThreadNode *node)
}
/*
* If the thread was suspended by another app thread,
* do nothing and report no error (we won't resume it later).
* JVMTI_ERROR_THREAD_SUSPENDED used to be possible when Thread.suspend()
* was still supported, but now we should no longer ever see it.
*/
if (error == JVMTI_ERROR_THREAD_SUSPENDED) {
error = JVMTI_ERROR_NONE;
}
JDI_ASSERT(error != JVMTI_ERROR_THREAD_SUSPENDED);
return error;
return error;
}
/*
@ -1099,8 +855,9 @@ resumeThreadByNode(ThreadNode *node)
if (node->suspendCount > 0) {
node->suspendCount--;
debugMonitorNotifyAll(threadLock);
if ((node->suspendCount == 0) && node->toBeResumed &&
!node->suspendOnStart) {
if ((node->suspendCount == 0) && node->toBeResumed) {
// We should never see both toBeResumed and suspendOnStart set true.
JDI_ASSERT(!node->suspendOnStart);
LOG_MISC(("thread=%p resumed", node->thread));
error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThread)
(gdata->jvmti, node->thread);
@ -1140,32 +897,6 @@ static void
preSuspend(void)
{
getLocks(); /* Avoid debugger deadlocks */
/*
* Delay any suspend while a call to java.lang.Thread.resume is in
* progress (not including those in suspended threads). The wait is
* timed because the threads suspended through
* java.lang.Thread.suspend won't result in a notify even though
* it may change the result of pendingAppResume()
*/
while (pendingAppResume(JNI_FALSE)) {
/*
* This is ugly but we need to release the locks from getLocks
* or else the notify will never happen. The locks must be
* released and reacquired in the right order. else deadlocks
* can happen. It is possible that, during this dance, the
* notify will be missed, but since the wait needs to be timed
* anyway, it won't be a disaster. Note that this code will
* execute only on very rare occasions anyway.
*/
releaseLocks();
debugMonitorEnter(threadLock);
debugMonitorTimedWait(threadLock, 1000);
debugMonitorExit(threadLock);
getLocks();
}
}
static void
@ -1232,17 +963,10 @@ resumeCopyHelper(JNIEnv *env, ThreadNode *node, void *arg)
* event came in during a suspendAll, but the helper hasn't
* completed the job yet. We decrement the count so the helper
* won't suspend this thread after we are done with the resumeAll.
* Another case to be handled here is when the debugger suspends
* the thread while the app has it suspended. In this case,
* the toBeResumed flag has been cleared indicating that
* the thread should not be resumed when the debugger does a resume.
* In this case, we also have to decrement the suspend count.
* If we don't then when the app resumes the thread and our Thread.resume
* bkpt handler is called, blockOnDebuggerSuspend will not resume
* the thread because suspendCount will be 1 meaning that the
* debugger has the thread suspended. See bug 6224859.
*/
if (node->suspendCount == 1 && (!node->toBeResumed || node->suspendOnStart)) {
if (node->suspendCount == 1 && node->suspendOnStart) {
// We should never see both toBeResumed and suspendOnStart set true.
JDI_ASSERT(!node->toBeResumed);
node->suspendCount--;
// TODO - vthread node cleanup: If this is a vthread, we should delete the node.
return JVMTI_ERROR_NONE;
@ -1255,13 +979,13 @@ resumeCopyHelper(JNIEnv *env, ThreadNode *node, void *arg)
/*
* This is tricky. A suspendCount of 1 and toBeResumed means that
* JVM/DI SuspendThread() or JVM/DI SuspendThreadList() was called
* on this thread. The check for !suspendOnStart is paranoia that
* we inherited from resumeThreadByNode().
* JVMTI SuspendThread() or JVMTI SuspendThreadList() was called
* on this thread.
*/
if (node->suspendCount == 1 && node->toBeResumed && !node->suspendOnStart) {
if (node->suspendCount == 1 && node->toBeResumed) {
// We should never see both toBeResumed and suspendOnStart set true.
JDI_ASSERT(!node->suspendOnStart);
jthread **listPtr = (jthread **)arg;
**listPtr = node->thread;
(*listPtr)++;
}
@ -1279,13 +1003,13 @@ resumeCountHelper(JNIEnv *env, ThreadNode *node, void *arg)
/*
* This is tricky. A suspendCount of 1 and toBeResumed means that
* JVM/DI SuspendThread() or JVM/DI SuspendThreadList() was called
* on this thread. The check for !suspendOnStart is paranoia that
* we inherited from resumeThreadByNode().
* JVMTI SuspendThread() or JVMTDI SuspendThreadList() was called
* on this thread.
*/
if (node->suspendCount == 1 && node->toBeResumed && !node->suspendOnStart) {
if (node->suspendCount == 1 && node->toBeResumed) {
// We should never see both toBeResumed and suspendOnStart set true.
JDI_ASSERT(!node->suspendOnStart);
jint *counter = (jint *)arg;
(*counter)++;
}
return JVMTI_ERROR_NONE;
@ -1387,7 +1111,7 @@ commonResumeList(JNIEnv *env)
LOG_MISC(("thread=%p resumed as part of list", node->thread));
/*
* resumeThreadByNode() assumes that JVM/DI ResumeThread()
* resumeThreadByNode() assumes that JVMTI ResumeThread()
* always works and does all the accounting updates. We do
* the same here. We also don't clear the error.
*/
@ -2388,59 +2112,6 @@ threadControl_onEventHandlerEntry(jbyte sessionID, EventInfo *evinfo, jobject cu
static void
doPendingTasks(JNIEnv *env, ThreadNode *node)
{
/* Deferred breakpoint handling for Thread.resume() */
if (node->handlingAppResume) {
jthread resumer = node->thread;
jthread resumee = getResumee(resumer);
if (resumer != NULL) {
/*
* trackAppResume indirectly acquires handlerLock. For proper lock
* ordering handlerLock has to be acquired before threadLock.
*/
debugMonitorExit(threadLock);
eventHandler_lock();
debugMonitorEnter(threadLock);
/*
* Track the resuming thread by marking it as being within
* a resume and by setting up for notification on
* a frame pop or exception. We won't allow the debugger
* to suspend threads while any thread is within a
* call to resume. This (along with the block below)
* ensures that when the debugger
* suspends a thread it will remain suspended.
*/
trackAppResume(resumer);
/*
* handlerLock is not needed anymore. We must release it before calling
* blockOnDebuggerSuspend() because it is required for resumes done by
* the debugger. If resumee is currently suspended by the debugger, then
* blockOnDebuggerSuspend() will block until a debugger resume is done.
* If it blocks while holding the handlerLock, then the resume will deadlock.
*/
eventHandler_unlock();
}
if (resumee != NULL) {
/*
* Hold up any attempt to resume as long as the debugger
* has suspended the resumee.
*/
blockOnDebuggerSuspend(resumee);
}
node->handlingAppResume = JNI_FALSE;
/*
* The blocks exit condition: resumee's suspendCount == 0.
*
* Debugger suspends are blocked if any thread is executing
* Thread.resume(), i.e. !handlingAppResume && frameDepth > 0.
*/
}
/*
* Take care of any pending interrupts/stops, and clear out
* info on pending interrupts/stops.
@ -2485,17 +2156,8 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread,
env = getEnv();
if (ei == EI_THREAD_END) {
jboolean inResume = (node->resumeFrameDepth > 0);
removeThread(env, node);
node = NULL; /* has been freed */
/*
* Clean up mechanism used to detect end of
* resume.
*/
if (inResume) {
notifyAppResumeComplete();
}
} else {
/* No point in doing this if the thread is about to die.*/
doPendingTasks(env, node);

@ -222,8 +222,6 @@ util_initialize(JNIEnv *env)
"<init>", "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V");
gdata->threadSetDaemon =
getMethod(env, gdata->threadClass, "setDaemon", "(Z)V");
gdata->threadResume =
getMethod(env, gdata->threadClass, "resume", "()V");
gdata->systemGetProperty =
getStaticMethod(env, gdata->systemClass,
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;");

@ -107,7 +107,6 @@ typedef struct {
jclass systemClass;
jmethodID threadConstructor;
jmethodID threadSetDaemon;
jmethodID threadResume;
jmethodID systemGetProperty;
jmethodID setProperty;
jthreadGroup systemThreadGroup;