/* * Copyright (c) 1998, 2007, 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 "eventHandler.h" #include "threadControl.h" #include "commonRef.h" #include "eventHelper.h" #include "stepControl.h" #include "invoker.h" #include "bag.h" #define HANDLING_EVENT(node) ((node)->current_ei != 0) /* * Collection of info for properly handling co-located events. * If the ei field is non-zero, then one of the possible * co-located events has been posted and the other fields describe * the event's location. */ typedef struct CoLocatedEventInfo_ { EventIndex ei; jclass clazz; jmethodID method; jlocation location; } CoLocatedEventInfo; /** * The main data structure in threadControl is the ThreadNode. * This is a per-thread structure that is allocated on the * first event that occurs in a thread. It is freed after the * thread's thread end event has completed processing. The * structure contains state information on its thread including * 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; unsigned int toBeResumed : 1; unsigned int pendingInterrupt : 1; unsigned int isDebugThread : 1; unsigned int suspendOnStart : 1; unsigned int isStarted : 1; unsigned int popFrameEvent : 1; unsigned int popFrameProceed : 1; unsigned int popFrameThread : 1; EventIndex current_ei; jobject pendingStop; jint suspendCount; jint resumeFrameDepth; /* !=0 => This thread is in a call to Thread.resume() */ jvmtiEventMode instructionStepMode; StepRequest currentStep; InvokeRequest currentInvoke; struct bag *eventBag; CoLocatedEventInfo cleInfo; struct ThreadNode *next; struct ThreadNode *prev; jlong frameGeneration; struct ThreadList *list; /* Tells us what list this thread is in */ } ThreadNode; static jint suspendAllCount; typedef struct ThreadList { ThreadNode *first; } ThreadList; /* * popFrameEventLock is used to notify that the event has been received */ static jrawMonitorID popFrameEventLock = NULL; /* * popFrameProceedLock is used to assure that the event thread is * re-suspended immediately after the event is acknowledged. */ 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); /* * Threads which have issued thread start events and not yet issued thread * end events are maintained in the "runningThreads" list. All other threads known * to this module are kept in the "otherThreads" list. */ static ThreadList runningThreads; static ThreadList otherThreads; #define MAX_DEBUG_THREADS 10 static int debugThreadCount; static jthread debugThreads[MAX_DEBUG_THREADS]; typedef struct DeferredEventMode { EventIndex ei; jvmtiEventMode mode; jthread thread; struct DeferredEventMode *next; } DeferredEventMode; typedef struct { DeferredEventMode *first; DeferredEventMode *last; } DeferredEventModeList; static DeferredEventModeList deferredEventModes; 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) { *pstate = 0; return JVMTI_FUNC_PTR(gdata->jvmti,GetThreadState) (gdata->jvmti, thread, pstate); } /* Set TLS on a specific jthread to the ThreadNode* */ static void setThreadLocalStorage(jthread thread, ThreadNode *node) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,SetThreadLocalStorage) (gdata->jvmti, thread, (void*)node); if ( error == JVMTI_ERROR_THREAD_NOT_ALIVE ) { /* Just return, thread hasn't started yet */ return; } else if ( error != JVMTI_ERROR_NONE ) { /* The jthread object must be valid, so this must be a fatal error */ EXIT_ERROR(error, "cannot set thread local storage"); } } /* Get TLS on a specific jthread, which is the ThreadNode* */ static ThreadNode * getThreadLocalStorage(jthread thread) { jvmtiError error; ThreadNode *node; node = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadLocalStorage) (gdata->jvmti, thread, (void**)&node); if ( error == JVMTI_ERROR_THREAD_NOT_ALIVE ) { /* Just return NULL, thread hasn't started yet */ return NULL; } else if ( error != JVMTI_ERROR_NONE ) { /* The jthread object must be valid, so this must be a fatal error */ EXIT_ERROR(error, "cannot get thread local storage"); } return node; } /* Search list for nodes that don't have TLS set and match this thread. * It assumed that this logic is never dealing with terminated threads, * since the ThreadEnd events always delete the ThreadNode while the * jthread is still alive. So we can only look at the ThreadNode's that * have never had their TLS set, making the search much faster. * But keep in mind, this kind of search should rarely be needed. */ static ThreadNode * nonTlsSearch(JNIEnv *env, ThreadList *list, jthread thread) { ThreadNode *node; for (node = list->first; node != NULL; node = node->next) { if (isSameObject(env, node->thread, thread)) { break; } } return node; } /* * These functions maintain the linked list of currently running threads. * All assume that the threadLock is held before calling. * If list==NULL, search both lists. */ static ThreadNode * findThread(ThreadList *list, jthread thread) { ThreadNode *node; /* Get thread local storage for quick thread -> node access */ node = getThreadLocalStorage(thread); /* In some rare cases we might get NULL, so we check the list manually for * any threads that we could match. */ if ( node == NULL ) { JNIEnv *env; env = getEnv(); if ( list != NULL ) { node = nonTlsSearch(env, list, thread); } else { node = nonTlsSearch(env, &runningThreads, thread); if ( node == NULL ) { node = nonTlsSearch(env, &otherThreads, thread); } } if ( node != NULL ) { /* Here we make another attempt to set TLS, it's ok if this fails */ setThreadLocalStorage(thread, (void*)node); } } /* If a list is supplied, only return ones in this list */ if ( node != NULL && list != NULL && node->list != list ) { return NULL; } return node; } /* Remove a ThreadNode from a ThreadList */ static void removeNode(ThreadList *list, ThreadNode *node) { ThreadNode *prev; ThreadNode *next; prev = node->prev; next = node->next; if ( prev != NULL ) { prev->next = next; } if ( next != NULL ) { next->prev = prev; } if ( prev == NULL ) { list->first = next; } node->next = NULL; node->prev = NULL; node->list = NULL; } /* Add a ThreadNode to a ThreadList */ static void addNode(ThreadList *list, ThreadNode *node) { node->next = NULL; node->prev = NULL; node->list = NULL; if ( list->first == NULL ) { list->first = node; } else { list->first->prev = node; node->next = list->first; list->first = node; } node->list = list; } static ThreadNode * insertThread(JNIEnv *env, ThreadList *list, jthread thread) { ThreadNode *node; struct bag *eventBag; node = findThread(list, thread); if (node == NULL) { node = jvmtiAllocate(sizeof(*node)); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"thread table entry"); return NULL; } (void)memset(node, 0, sizeof(*node)); eventBag = eventHelper_createEventBag(); if (eventBag == NULL) { jvmtiDeallocate(node); EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"thread table entry"); return NULL; } /* * Init all flags false, all refs NULL, all counts 0 */ saveGlobalRef(env, thread, &(node->thread)); if (node->thread == NULL) { jvmtiDeallocate(node); bagDestroyBag(eventBag); EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"thread table entry"); return NULL; } /* * Remember if it is a debug thread */ if (threadControl_isDebugThread(node->thread)) { node->isDebugThread = JNI_TRUE; } else if (suspendAllCount > 0){ /* * If there is a pending suspendAll, all new threads should * be initialized as if they were suspended by the suspendAll, * and the thread will need to be suspended when it starts. */ node->suspendCount = suspendAllCount; node->suspendOnStart = JNI_TRUE; } node->current_ei = 0; node->instructionStepMode = JVMTI_DISABLE; node->eventBag = eventBag; addNode(list, node); /* Set thread local storage for quick thread -> node access. * Some threads may not be in a state that allows setting of TLS, * which is ok, see findThread, it deals with threads without TLS set. */ setThreadLocalStorage(node->thread, (void*)node); } return node; } static void clearThread(JNIEnv *env, ThreadNode *node) { if (node->pendingStop != NULL) { tossGlobalRef(env, &(node->pendingStop)); } stepControl_clearRequest(node->thread, &node->currentStep); if (node->isDebugThread) { (void)threadControl_removeDebugThread(node->thread); } /* Clear out TLS on this thread (just a cleanup action) */ setThreadLocalStorage(node->thread, NULL); tossGlobalRef(env, &(node->thread)); bagDestroyBag(node->eventBag); jvmtiDeallocate(node); } static void removeThread(JNIEnv *env, ThreadList *list, jthread thread) { ThreadNode *node; node = findThread(list, thread); if (node != NULL) { removeNode(list, node); clearThread(env, node); } } static void removeResumed(JNIEnv *env, ThreadList *list) { ThreadNode *node; node = list->first; while (node != NULL) { ThreadNode *temp = node->next; if (node->suspendCount == 0) { removeThread(env, list, node->thread); } node = temp; } } static void moveNode(ThreadList *source, ThreadList *dest, ThreadNode *node) { removeNode(source, node); JDI_ASSERT(findThread(dest, node->thread) == NULL); addNode(dest, node); } typedef jvmtiError (*ThreadEnumerateFunction)(JNIEnv *, ThreadNode *, void *); static jvmtiError enumerateOverThreadList(JNIEnv *env, ThreadList *list, ThreadEnumerateFunction function, void *arg) { ThreadNode *node; jvmtiError error = JVMTI_ERROR_NONE; for (node = list->first; node != NULL; node = node->next) { error = (*function)(env, node, arg); if ( error != JVMTI_ERROR_NONE ) { break; } } return error; } static void insertEventMode(DeferredEventModeList *list, DeferredEventMode *eventMode) { if (list->last != NULL) { list->last->next = eventMode; } else { list->first = eventMode; } list->last = eventMode; } static void removeEventMode(DeferredEventModeList *list, DeferredEventMode *eventMode, DeferredEventMode *prev) { if (prev == NULL) { list->first = eventMode->next; } else { prev->next = eventMode->next; } if (eventMode->next == NULL) { list->last = prev; } } static jvmtiError addDeferredEventMode(JNIEnv *env, jvmtiEventMode mode, EventIndex ei, jthread thread) { DeferredEventMode *eventMode; /*LINTED*/ eventMode = jvmtiAllocate((jint)sizeof(DeferredEventMode)); if (eventMode == NULL) { return AGENT_ERROR_OUT_OF_MEMORY; } eventMode->thread = NULL; saveGlobalRef(env, thread, &(eventMode->thread)); eventMode->mode = mode; eventMode->ei = ei; eventMode->next = NULL; insertEventMode(&deferredEventModes, eventMode); return JVMTI_ERROR_NONE; } static void freeDeferredEventModes(JNIEnv *env) { DeferredEventMode *eventMode; eventMode = deferredEventModes.first; while (eventMode != NULL) { DeferredEventMode *next; next = eventMode->next; tossGlobalRef(env, &(eventMode->thread)); jvmtiDeallocate(eventMode); eventMode = next; } deferredEventModes.first = NULL; deferredEventModes.last = NULL; } static jvmtiError threadSetEventNotificationMode(ThreadNode *node, jvmtiEventMode mode, EventIndex ei, jthread thread) { jvmtiError error; /* record single step mode */ if (ei == EI_SINGLE_STEP) { node->instructionStepMode = mode; } error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventNotificationMode) (gdata->jvmti, mode, eventIndex2jvmti(ei), thread); return error; } static void processDeferredEventModes(JNIEnv *env, jthread thread, ThreadNode *node) { jvmtiError error; DeferredEventMode *eventMode; DeferredEventMode *prev; prev = NULL; eventMode = deferredEventModes.first; while (eventMode != NULL) { DeferredEventMode *next = eventMode->next; if (isSameObject(env, thread, eventMode->thread)) { error = threadSetEventNotificationMode(node, eventMode->mode, eventMode->ei, eventMode->thread); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "cannot process deferred thread event notifications at thread start"); } removeEventMode(&deferredEventModes, eventMode, prev); tossGlobalRef(env, &(eventMode->thread)); jvmtiDeallocate(eventMode); } else { prev = eventMode; } eventMode = next; } } static void getLocks(void) { /* * Anything which might be locked as part of the handling of * a JVMTI event (which means: might be locked by an application * thread) needs to be grabbed here. This allows thread control * code to safely suspend and resume the application threads * while ensuring they don't hold a critical lock. */ eventHandler_lock(); invoker_lock(); eventHelper_lock(); stepControl_lock(); commonRef_lock(); debugMonitorEnter(threadLock); } static void releaseLocks(void) { debugMonitorExit(threadLock); commonRef_unlock(); stepControl_unlock(); eventHelper_unlock(); invoker_unlock(); eventHandler_unlock(); } void threadControl_initialize(void) { jlocation unused; jvmtiError error; suspendAllCount = 0; runningThreads.first = NULL; otherThreads.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"); } if (!(state & JVMTI_THREAD_STATE_SUSPENDED)) { return JNI_TRUE; } } } node = node->next; } 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; } } } static void handleAppResumeCompletion(JNIEnv *env, EventInfo *evinfo, HandlerNode *handlerNode, struct bag *eventBag) { ThreadNode *node; jthread thread; thread = evinfo->thread; debugMonitorEnter(threadLock); node = findThread(&runningThreads, 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); } } } static void trackAppResume(jthread thread) { jvmtiError error; FrameNumber fnum; ThreadNode *node; fnum = 0; node = findThread(&runningThreads, 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; } } } } static void handleAppResumeBreakpoint(JNIEnv *env, EventInfo *evinfo, HandlerNode *handlerNode, struct bag *eventBag) { jthread resumer = evinfo->thread; jthread resumee = getResumee(resumer); debugMonitorEnter(threadLock); if (resumee != NULL) { /* * Hold up any attempt to resume as long as the debugger * has suspended the resumee. */ blockOnDebuggerSuspend(resumee); } if (resumer != NULL) { /* * 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 above) * ensures that when the debugger * suspends a thread it will remain suspended. */ trackAppResume(resumer); } 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 threadControl_onHook(void) { /* * As soon as the event hook is in place, we need to initialize * the thread list with already-existing threads. The threadLock * has been held since initialize, so we don't need to worry about * insertions or deletions from the event handlers while we do this */ JNIEnv *env; env = getEnv(); /* * Prevent any event processing until OnHook has been called */ debugMonitorEnter(threadLock); WITH_LOCAL_REFS(env, 1) { jint threadCount; jthread *threads; threads = allThreads(&threadCount); if (threads == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"thread table"); } else { int i; for (i = 0; i < threadCount; i++) { ThreadNode *node; jthread thread = threads[i]; node = insertThread(env, &runningThreads, thread); /* * This is a tiny bit risky. We have to assume that the * pre-existing threads have been started because we * can't rely on a thread start event for them. The chances * of a problem related to this are pretty slim though, and * there's really no choice because without setting this flag * there is no way to enable stepping and other events on * the threads that already exist (e.g. the finalizer thread). */ node->isStarted = JNI_TRUE; } } } END_WITH_LOCAL_REFS(env) debugMonitorExit(threadLock); } static jvmtiError commonSuspendByNode(ThreadNode *node) { jvmtiError error; LOG_MISC(("thread=%p suspended", node->thread)); error = JVMTI_FUNC_PTR(gdata->jvmti,SuspendThread) (gdata->jvmti, node->thread); /* * Mark for resume only if suspend succeeded */ if (error == JVMTI_ERROR_NONE) { node->toBeResumed = JNI_TRUE; } /* * If the thread was suspended by another app thread, * do nothing and report no error (we won't resume it later). */ if (error == JVMTI_ERROR_THREAD_SUSPENDED) { error = JVMTI_ERROR_NONE; } return error; } /* * Deferred suspends happen when the suspend is attempted on a thread * that is not started. Bookkeeping (suspendCount,etc.) * is handled by the original request, and once the thread actually * starts, an actual suspend is attempted. This function does the * deferred suspend without changing the bookkeeping that is already * in place. */ static jint deferredSuspendThreadByNode(ThreadNode *node) { jvmtiError error; error = JVMTI_ERROR_NONE; if (node->isDebugThread) { /* Ignore requests for suspending debugger threads */ return JVMTI_ERROR_NONE; } /* * Do the actual suspend only if a subsequent resume hasn't * made it irrelevant. */ if (node->suspendCount > 0) { error = commonSuspendByNode(node); /* * Attempt to clean up from any error by decrementing the * suspend count. This compensates for the increment that * happens when suspendOnStart is set to true. */ if (error != JVMTI_ERROR_NONE) { node->suspendCount--; } } node->suspendOnStart = JNI_FALSE; debugMonitorNotifyAll(threadLock); return error; } static jvmtiError suspendThreadByNode(ThreadNode *node) { jvmtiError error = JVMTI_ERROR_NONE; if (node->isDebugThread) { /* Ignore requests for suspending debugger threads */ return JVMTI_ERROR_NONE; } /* * Just increment the suspend count if we are waiting * for a deferred suspend. */ if (node->suspendOnStart) { node->suspendCount++; return JVMTI_ERROR_NONE; } if (node->suspendCount == 0) { error = commonSuspendByNode(node); if (error == JVMTI_ERROR_THREAD_NOT_ALIVE) { /* * This error means that the thread is either a zombie or not yet * started. In either case, we ignore the error. If the thread * is a zombie, suspend/resume are no-ops. If the thread is not * started, it will be suspended for real during the processing * of its thread start event. */ node->suspendOnStart = JNI_TRUE; error = JVMTI_ERROR_NONE; } } if (error == JVMTI_ERROR_NONE) { node->suspendCount++; } debugMonitorNotifyAll(threadLock); return error; } static jvmtiError resumeThreadByNode(ThreadNode *node) { jvmtiError error = JVMTI_ERROR_NONE; if (node->isDebugThread) { /* never suspended by debugger => don't ever try to resume */ return JVMTI_ERROR_NONE; } if (node->suspendCount > 0) { node->suspendCount--; debugMonitorNotifyAll(threadLock); if ((node->suspendCount == 0) && node->toBeResumed && !node->suspendOnStart) { LOG_MISC(("thread=%p resumed", node->thread)); error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThread) (gdata->jvmti, node->thread); node->frameGeneration++; /* Increment on each resume */ node->toBeResumed = JNI_FALSE; if (error == JVMTI_ERROR_THREAD_NOT_ALIVE && !node->isStarted) { /* * We successfully "suspended" this thread, but * we never received a THREAD_START event for it. * Since the thread never ran, we can ignore our * failure to resume the thread. */ error = JVMTI_ERROR_NONE; } } } return error; } /* * Functions which respond to user requests to suspend/resume * threads. * Suspends and resumes add and subtract from a count respectively. * The thread is only suspended when the count goes from 0 to 1 and * resumed only when the count goes from 1 to 0. * * These functions suspend and resume application threads * without changing the * state of threads that were already suspended beforehand. * They must not be called from an application thread because * that thread may be suspended somewhere in the middle of things. */ 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 postSuspend(void) { releaseLocks(); } /* * This function must be called after preSuspend and before postSuspend. */ static jvmtiError commonSuspend(JNIEnv *env, jthread thread, jboolean deferred) { ThreadNode *node; /* * If the thread is not between its start and end events, we should * still suspend it. To keep track of things, add the thread * to a separate list of threads so that we'll resume it later. */ node = findThread(&runningThreads, thread); if (node == NULL) { node = insertThread(env, &otherThreads, thread); } if ( deferred ) { return deferredSuspendThreadByNode(node); } else { return suspendThreadByNode(node); } } static jvmtiError resumeCopyHelper(JNIEnv *env, ThreadNode *node, void *arg) { if (node->isDebugThread) { /* never suspended by debugger => don't ever try to resume */ return JVMTI_ERROR_NONE; } if (node->suspendCount > 1) { node->suspendCount--; /* nested suspend so just undo one level */ return JVMTI_ERROR_NONE; } /* * This thread was marked for suspension since its THREAD_START * 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)) { node->suspendCount--; return JVMTI_ERROR_NONE; } if (arg == NULL) { /* nothing to hard resume so we're done */ return JVMTI_ERROR_NONE; } /* * 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(). */ if (node->suspendCount == 1 && node->toBeResumed && !node->suspendOnStart) { jthread **listPtr = (jthread **)arg; **listPtr = node->thread; (*listPtr)++; } return JVMTI_ERROR_NONE; } static jvmtiError resumeCountHelper(JNIEnv *env, ThreadNode *node, void *arg) { if (node->isDebugThread) { /* never suspended by debugger => don't ever try to resume */ return JVMTI_ERROR_NONE; } /* * 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(). */ if (node->suspendCount == 1 && node->toBeResumed && !node->suspendOnStart) { jint *counter = (jint *)arg; (*counter)++; } return JVMTI_ERROR_NONE; } static void * newArray(jint length, size_t nbytes) { void *ptr; ptr = jvmtiAllocate(length*(jint)nbytes); if ( ptr != NULL ) { (void)memset(ptr, 0, length*nbytes); } return ptr; } static void deleteArray(void *ptr) { jvmtiDeallocate(ptr); } /* * This function must be called with the threadLock held. * * Two facts conspire to make this routine complicated: * * 1) the VM doesn't support nested external suspend * 2) the original resumeAll code structure doesn't retrieve the * entire thread list from JVMTI so we use the runningThreads * list and two helpers to get the job done. * * Because we hold the threadLock, state seen by resumeCountHelper() * is the same state seen in resumeCopyHelper(). resumeCountHelper() * just counts up the number of threads to be hard resumed. * resumeCopyHelper() does the accounting for nested suspends and * special cases and, finally, populates the list of hard resume * threads to be passed to ResumeThreadList(). * * At first glance, you might think that the accounting could be done * in resumeCountHelper(), but then resumeCopyHelper() would see * "post-resume" state in the accounting values (suspendCount and * toBeResumed) and would not be able to distinguish between a thread * that needs a hard resume versus a thread that is already running. */ static jvmtiError commonResumeList(JNIEnv *env) { jvmtiError error; jint i; jint reqCnt; jthread *reqList; jthread *reqPtr; jvmtiError *results; reqCnt = 0; /* count number of threads to hard resume */ (void) enumerateOverThreadList(env, &runningThreads, resumeCountHelper, &reqCnt); if (reqCnt == 0) { /* nothing to hard resume so do just the accounting part */ (void) enumerateOverThreadList(env, &runningThreads, resumeCopyHelper, NULL); return JVMTI_ERROR_NONE; } /*LINTED*/ reqList = newArray(reqCnt, sizeof(jthread)); if (reqList == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"resume request list"); } /*LINTED*/ results = newArray(reqCnt, sizeof(jvmtiError)); if (results == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"resume list"); } /* copy the jthread values for threads to hard resume */ reqPtr = reqList; (void) enumerateOverThreadList(env, &runningThreads, resumeCopyHelper, &reqPtr); error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThreadList) (gdata->jvmti, reqCnt, reqList, results); for (i = 0; i < reqCnt; i++) { ThreadNode *node; node = findThread(&runningThreads, reqList[i]); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_INVALID_THREAD,"missing entry in running thread table"); } LOG_MISC(("thread=%p resumed as part of list", node->thread)); /* * resumeThreadByNode() assumes that JVM/DI ResumeThread() * always works and does all the accounting updates. We do * the same here. We also don't clear the error. */ node->suspendCount--; node->toBeResumed = JNI_FALSE; node->frameGeneration++; /* Increment on each resume */ } deleteArray(results); deleteArray(reqList); debugMonitorNotifyAll(threadLock); return error; } /* * This function must be called after preSuspend and before postSuspend. */ static jvmtiError commonSuspendList(JNIEnv *env, jint initCount, jthread *initList) { jvmtiError error; jint i; jint reqCnt; jthread *reqList; error = JVMTI_ERROR_NONE; reqCnt = 0; reqList = newArray(initCount, sizeof(jthread)); if (reqList == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"request list"); } /* * Go through the initial list and see if we have anything to suspend. */ for (i = 0; i < initCount; i++) { ThreadNode *node; /* * If the thread is not between its start and end events, we should * still suspend it. To keep track of things, add the thread * to a separate list of threads so that we'll resume it later. */ node = findThread(&runningThreads, initList[i]); if (node == NULL) { node = insertThread(env, &otherThreads, initList[i]); } if (node->isDebugThread) { /* Ignore requests for suspending debugger threads */ continue; } /* * Just increment the suspend count if we are waiting * for a deferred suspend or if this is a nested suspend. */ if (node->suspendOnStart || node->suspendCount > 0) { node->suspendCount++; continue; } if (node->suspendCount == 0) { /* thread is not suspended yet so put it on the request list */ reqList[reqCnt++] = initList[i]; } } if (reqCnt > 0) { jvmtiError *results = newArray(reqCnt, sizeof(jvmtiError)); if (results == NULL) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"suspend list results"); } /* * We have something to suspend so try to do it. */ error = JVMTI_FUNC_PTR(gdata->jvmti,SuspendThreadList) (gdata->jvmti, reqCnt, reqList, results); for (i = 0; i < reqCnt; i++) { ThreadNode *node; node = findThread(NULL, reqList[i]); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_INVALID_THREAD,"missing entry in thread tables"); } LOG_MISC(("thread=%p suspended as part of list", node->thread)); if (results[i] == JVMTI_ERROR_NONE) { /* thread was suspended as requested */ node->toBeResumed = JNI_TRUE; } else if (results[i] == JVMTI_ERROR_THREAD_SUSPENDED) { /* * If the thread was suspended by another app thread, * do nothing and report no error (we won't resume it later). */ results[i] = JVMTI_ERROR_NONE; } else if (results[i] == JVMTI_ERROR_THREAD_NOT_ALIVE) { /* * This error means that the suspend request failed * because the thread is either a zombie or not yet * started. In either case, we ignore the error. If the * thread is a zombie, suspend/resume are no-ops. If the * thread is not started, it will be suspended for real * during the processing of its thread start event. */ node->suspendOnStart = JNI_TRUE; results[i] = JVMTI_ERROR_NONE; } /* count real, app and deferred (suspendOnStart) suspensions */ if (results[i] == JVMTI_ERROR_NONE) { node->suspendCount++; } } deleteArray(results); } deleteArray(reqList); debugMonitorNotifyAll(threadLock); return error; } static jvmtiError commonResume(jthread thread) { jvmtiError error; ThreadNode *node; /* * The thread is normally between its start and end events, but if * not, check the auxiliary list used by threadControl_suspendThread. */ node = findThread(NULL, thread); /* * If the node is in neither list, the debugger never suspended * this thread, so do nothing. */ error = JVMTI_ERROR_NONE; if (node != NULL) { error = resumeThreadByNode(node); } return error; } jvmtiError threadControl_suspendThread(jthread thread, jboolean deferred) { jvmtiError error; JNIEnv *env; env = getEnv(); log_debugee_location("threadControl_suspendThread()", thread, NULL, 0); preSuspend(); error = commonSuspend(env, thread, deferred); postSuspend(); return error; } jvmtiError threadControl_resumeThread(jthread thread, jboolean do_unblock) { jvmtiError error; JNIEnv *env; env = getEnv(); log_debugee_location("threadControl_resumeThread()", thread, NULL, 0); eventHandler_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); error = commonResume(thread); removeResumed(env, &otherThreads); debugMonitorExit(threadLock); eventHandler_unlock(); if (do_unblock) { /* let eventHelper.c: commandLoop() know we resumed one thread */ unblockCommandLoop(); } return error; } jvmtiError threadControl_suspendCount(jthread thread, jint *count) { jvmtiError error; ThreadNode *node; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node == NULL) { node = findThread(&otherThreads, thread); } error = JVMTI_ERROR_NONE; if (node != NULL) { *count = node->suspendCount; } else { /* * If the node is in neither list, the debugger never suspended * this thread, so the suspend count is 0. */ *count = 0; } debugMonitorExit(threadLock); return error; } static jboolean contains(JNIEnv *env, jthread *list, jint count, jthread item) { int i; for (i = 0; i < count; i++) { if (isSameObject(env, list[i], item)) { return JNI_TRUE; } } return JNI_FALSE; } typedef struct { jthread *list; jint count; } SuspendAllArg; static jvmtiError suspendAllHelper(JNIEnv *env, ThreadNode *node, void *arg) { SuspendAllArg *saArg = (SuspendAllArg *)arg; jvmtiError error = JVMTI_ERROR_NONE; jthread *list = saArg->list; jint count = saArg->count; if (!contains(env, list, count, node->thread)) { error = commonSuspend(env, node->thread, JNI_FALSE); } return error; } jvmtiError threadControl_suspendAll(void) { jvmtiError error; JNIEnv *env; env = getEnv(); log_debugee_location("threadControl_suspendAll()", NULL, NULL, 0); preSuspend(); /* * Get a list of all threads and suspend them. */ WITH_LOCAL_REFS(env, 1) { jthread *threads; jint count; threads = allThreads(&count); if (threads == NULL) { error = AGENT_ERROR_OUT_OF_MEMORY; goto err; } if (canSuspendResumeThreadLists()) { error = commonSuspendList(env, count, threads); if (error != JVMTI_ERROR_NONE) { goto err; } } else { int i; for (i = 0; i < count; i++) { error = commonSuspend(env, threads[i], JNI_FALSE); if (error != JVMTI_ERROR_NONE) { goto err; } } } /* * Update the suspend count of any threads not yet (or no longer) * in the thread list above. */ { SuspendAllArg arg; arg.list = threads; arg.count = count; error = enumerateOverThreadList(env, &otherThreads, suspendAllHelper, &arg); } if (error == JVMTI_ERROR_NONE) { suspendAllCount++; } err: ; } END_WITH_LOCAL_REFS(env) postSuspend(); return error; } static jvmtiError resumeHelper(JNIEnv *env, ThreadNode *node, void *ignored) { /* * Since this helper is called with the threadLock held, we * don't need to recheck to see if the node is still on one * of the two thread lists. */ return resumeThreadByNode(node); } jvmtiError threadControl_resumeAll(void) { jvmtiError error; JNIEnv *env; env = getEnv(); log_debugee_location("threadControl_resumeAll()", NULL, NULL, 0); eventHandler_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); /* * Resume only those threads that the debugger has suspended. All * such threads must have a node in one of the thread lists, so there's * no need to get the whole thread list from JVMTI (unlike * suspendAll). */ if (canSuspendResumeThreadLists()) { error = commonResumeList(env); } else { error = enumerateOverThreadList(env, &runningThreads, resumeHelper, NULL); } if ((error == JVMTI_ERROR_NONE) && (otherThreads.first != NULL)) { error = enumerateOverThreadList(env, &otherThreads, resumeHelper, NULL); removeResumed(env, &otherThreads); } if (suspendAllCount > 0) { suspendAllCount--; } debugMonitorExit(threadLock); eventHandler_unlock(); /* let eventHelper.c: commandLoop() know we are resuming */ unblockCommandLoop(); return error; } StepRequest * threadControl_getStepRequest(jthread thread) { ThreadNode *node; StepRequest *step; step = NULL; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL) { step = &node->currentStep; } debugMonitorExit(threadLock); return step; } InvokeRequest * threadControl_getInvokeRequest(jthread thread) { ThreadNode *node; InvokeRequest *request; request = NULL; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL) { request = &node->currentInvoke; } debugMonitorExit(threadLock); return request; } jvmtiError threadControl_addDebugThread(jthread thread) { jvmtiError error; debugMonitorEnter(threadLock); if (debugThreadCount >= MAX_DEBUG_THREADS) { error = AGENT_ERROR_OUT_OF_MEMORY; } else { JNIEnv *env; env = getEnv(); debugThreads[debugThreadCount] = NULL; saveGlobalRef(env, thread, &(debugThreads[debugThreadCount])); if (debugThreads[debugThreadCount] == NULL) { error = AGENT_ERROR_OUT_OF_MEMORY; } else { debugThreadCount++; error = JVMTI_ERROR_NONE; } } debugMonitorExit(threadLock); return error; } static jvmtiError threadControl_removeDebugThread(jthread thread) { jvmtiError error; JNIEnv *env; int i; error = AGENT_ERROR_INVALID_THREAD; env = getEnv(); debugMonitorEnter(threadLock); for (i = 0; i< debugThreadCount; i++) { if (isSameObject(env, thread, debugThreads[i])) { int j; tossGlobalRef(env, &(debugThreads[i])); for (j = i+1; j < debugThreadCount; j++) { debugThreads[j-1] = debugThreads[j]; } debugThreadCount--; error = JVMTI_ERROR_NONE; break; } } debugMonitorExit(threadLock); return error; } jboolean threadControl_isDebugThread(jthread thread) { int i; jboolean rc; JNIEnv *env; rc = JNI_FALSE; env = getEnv(); debugMonitorEnter(threadLock); for (i = 0; i < debugThreadCount; i++) { if (isSameObject(env, thread, debugThreads[i])) { rc = JNI_TRUE; break; } } debugMonitorExit(threadLock); return rc; } static void initLocks(void) { if (popFrameEventLock == NULL) { popFrameEventLock = debugMonitorCreate("JDWP PopFrame Event Lock"); popFrameProceedLock = debugMonitorCreate("JDWP PopFrame Proceed Lock"); } } static jboolean getPopFrameThread(jthread thread) { jboolean popFrameThread; debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node == NULL) { popFrameThread = JNI_FALSE; } else { popFrameThread = node->popFrameThread; } } debugMonitorExit(threadLock); return popFrameThread; } static void setPopFrameThread(jthread thread, jboolean value) { debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"entry in thread table"); } else { node->popFrameThread = value; } } debugMonitorExit(threadLock); } static jboolean getPopFrameEvent(jthread thread) { jboolean popFrameEvent; debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node == NULL) { popFrameEvent = JNI_FALSE; EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"entry in thread table"); } else { popFrameEvent = node->popFrameEvent; } } debugMonitorExit(threadLock); return popFrameEvent; } static void setPopFrameEvent(jthread thread, jboolean value) { debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"entry in thread table"); } else { node->popFrameEvent = value; node->frameGeneration++; /* Increment on each resume */ } } debugMonitorExit(threadLock); } static jboolean getPopFrameProceed(jthread thread) { jboolean popFrameProceed; debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node == NULL) { popFrameProceed = JNI_FALSE; EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"entry in thread table"); } else { popFrameProceed = node->popFrameProceed; } } debugMonitorExit(threadLock); return popFrameProceed; } static void setPopFrameProceed(jthread thread, jboolean value) { debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"entry in thread table"); } else { node->popFrameProceed = value; } } debugMonitorExit(threadLock); } /** * Special event handler for events on the popped thread * that occur during the pop operation. */ static void popFrameCompleteEvent(jthread thread) { debugMonitorEnter(popFrameProceedLock); { /* notify that we got the event */ debugMonitorEnter(popFrameEventLock); { setPopFrameEvent(thread, JNI_TRUE); debugMonitorNotify(popFrameEventLock); } debugMonitorExit(popFrameEventLock); /* make sure we get suspended again */ setPopFrameProceed(thread, JNI_FALSE); while (getPopFrameProceed(thread) == JNI_FALSE) { debugMonitorWait(popFrameProceedLock); } } debugMonitorExit(popFrameProceedLock); } /** * Pop one frame off the stack of thread. * popFrameEventLock is already held */ static jvmtiError popOneFrame(jthread thread) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,PopFrame)(gdata->jvmti, thread); if (error != JVMTI_ERROR_NONE) { return error; } /* resume the popped thread so that the pop occurs and so we */ /* will get the event (step or method entry) after the pop */ LOG_MISC(("thread=%p resumed in popOneFrame", thread)); error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThread)(gdata->jvmti, thread); if (error != JVMTI_ERROR_NONE) { return error; } /* wait for the event to occur */ setPopFrameEvent(thread, JNI_FALSE); while (getPopFrameEvent(thread) == JNI_FALSE) { debugMonitorWait(popFrameEventLock); } /* make sure not to suspend until the popped thread is on the wait */ debugMonitorEnter(popFrameProceedLock); { /* return popped thread to suspended state */ LOG_MISC(("thread=%p suspended in popOneFrame", thread)); error = JVMTI_FUNC_PTR(gdata->jvmti,SuspendThread)(gdata->jvmti, thread); /* notify popped thread so it can proceed when resumed */ setPopFrameProceed(thread, JNI_TRUE); debugMonitorNotify(popFrameProceedLock); } debugMonitorExit(popFrameProceedLock); return error; } /** * pop frames of the stack of 'thread' until 'frame' is popped. */ jvmtiError threadControl_popFrames(jthread thread, FrameNumber fnum) { jvmtiError error; jvmtiEventMode prevStepMode; jint framesPopped = 0; jint popCount; jboolean prevInvokeRequestMode; log_debugee_location("threadControl_popFrames()", thread, NULL, 0); initLocks(); /* compute the number of frames to pop */ popCount = fnum+1; if (popCount < 1) { return AGENT_ERROR_NO_MORE_FRAMES; } /* enable instruction level single step, but first note prev value */ prevStepMode = threadControl_getInstructionStepMode(thread); /* * Fix bug 6517249. The pop processing will disable invokes, * so remember if invokes are enabled now and restore * that state after we finish popping. */ prevInvokeRequestMode = invoker_isEnabled(thread); error = threadControl_setEventMode(JVMTI_ENABLE, EI_SINGLE_STEP, thread); if (error != JVMTI_ERROR_NONE) { return error; } /* Inform eventHandler logic we are in a popFrame for this thread */ debugMonitorEnter(popFrameEventLock); { setPopFrameThread(thread, JNI_TRUE); /* pop frames using single step */ while (framesPopped++ < popCount) { error = popOneFrame(thread); if (error != JVMTI_ERROR_NONE) { break; } } setPopFrameThread(thread, JNI_FALSE); } debugMonitorExit(popFrameEventLock); /* Reset StepRequest info (fromLine and stackDepth) after popframes * only if stepping is enabled. */ if (prevStepMode == JVMTI_ENABLE) { stepControl_resetRequest(thread); } if (prevInvokeRequestMode) { invoker_enableInvokeRequests(thread); } /* restore state */ (void)threadControl_setEventMode(prevStepMode, EI_SINGLE_STEP, thread); return error; } /* Check to see if any events are being consumed by a popFrame(). */ static jboolean checkForPopFrameEvents(JNIEnv *env, EventIndex ei, jthread thread) { if ( getPopFrameThread(thread) ) { switch (ei) { case EI_THREAD_START: /* Excuse me? */ EXIT_ERROR(AGENT_ERROR_INTERNAL, "thread start during pop frame"); break; case EI_THREAD_END: /* Thread wants to end? let it. */ setPopFrameThread(thread, JNI_FALSE); popFrameCompleteEvent(thread); break; case EI_SINGLE_STEP: /* This is an event we requested to mark the */ /* completion of the pop frame */ popFrameCompleteEvent(thread); return JNI_TRUE; case EI_BREAKPOINT: case EI_EXCEPTION: case EI_FIELD_ACCESS: case EI_FIELD_MODIFICATION: case EI_METHOD_ENTRY: case EI_METHOD_EXIT: /* Tell event handler to assume event has been consumed. */ return JNI_TRUE; default: break; } } /* Pretend we were never called */ return JNI_FALSE; } struct bag * threadControl_onEventHandlerEntry(jbyte sessionID, EventIndex ei, jthread thread, jobject currentException) { ThreadNode *node; JNIEnv *env; struct bag *eventBag; jthread threadToSuspend; jboolean consumed; env = getEnv(); threadToSuspend = NULL; log_debugee_location("threadControl_onEventHandlerEntry()", thread, NULL, 0); /* Events during pop commands may need to be ignored here. */ consumed = checkForPopFrameEvents(env, ei, thread); if ( consumed ) { /* Always restore any exception (see below). */ if (currentException != NULL) { JNI_FUNC_PTR(env,Throw)(env, currentException); } else { JNI_FUNC_PTR(env,ExceptionClear)(env); } return NULL; } debugMonitorEnter(threadLock); /* * Check the list of unknown threads maintained by suspend * and resume. If this thread is currently present in the * list, it should be * moved to the runningThreads list, since it is a * well-known thread now. */ node = findThread(&otherThreads, thread); if (node != NULL) { moveNode(&otherThreads, &runningThreads, node); } else { /* * Get a thread node for the reporting thread. For thread start * events, or if this event precedes a thread start event, * the thread node may need to be created. * * It is possible for certain events (notably method entry/exit) * to precede thread start for some VM implementations. */ node = insertThread(env, &runningThreads, thread); } if (ei == EI_THREAD_START) { node->isStarted = JNI_TRUE; processDeferredEventModes(env, thread, node); } node->current_ei = ei; eventBag = node->eventBag; if (node->suspendOnStart) { threadToSuspend = node->thread; } debugMonitorExit(threadLock); if (threadToSuspend != NULL) { /* * An attempt was made to suspend this thread before it started. * We must suspend it now, before it starts to run. This must * be done with no locks held. */ eventHelper_suspendThread(sessionID, threadToSuspend); } return eventBag; } static void doPendingTasks(JNIEnv *env, ThreadNode *node) { /* * Take care of any pending interrupts/stops, and clear out * info on pending interrupts/stops. */ if (node->pendingInterrupt) { JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread) (gdata->jvmti, node->thread); /* * TO DO: Log error */ node->pendingInterrupt = JNI_FALSE; } if (node->pendingStop != NULL) { JVMTI_FUNC_PTR(gdata->jvmti,StopThread) (gdata->jvmti, node->thread, node->pendingStop); /* * TO DO: Log error */ tossGlobalRef(env, &(node->pendingStop)); } } void threadControl_onEventHandlerExit(EventIndex ei, jthread thread, struct bag *eventBag) { ThreadNode *node; log_debugee_location("threadControl_onEventHandlerExit()", thread, NULL, 0); if (ei == EI_THREAD_END) { eventHandler_lock(); /* for proper lock order */ } debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node == NULL) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted"); } else { JNIEnv *env; env = getEnv(); if (ei == EI_THREAD_END) { jboolean inResume = (node->resumeFrameDepth > 0); removeThread(env, &runningThreads, thread); 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); node->eventBag = eventBag; node->current_ei = 0; } } debugMonitorExit(threadLock); if (ei == EI_THREAD_END) { eventHandler_unlock(); } } /* Returns JDWP flavored status and status flags. */ jvmtiError threadControl_applicationThreadStatus(jthread thread, jdwpThreadStatus *pstatus, jint *statusFlags) { ThreadNode *node; jvmtiError error; jint state; log_debugee_location("threadControl_applicationThreadStatus()", thread, NULL, 0); debugMonitorEnter(threadLock); error = threadState(thread, &state); *pstatus = map2jdwpThreadStatus(state); *statusFlags = map2jdwpSuspendStatus(state); if (error == JVMTI_ERROR_NONE) { node = findThread(&runningThreads, thread); if ((node != NULL) && HANDLING_EVENT(node)) { /* * While processing an event, an application thread is always * considered to be running even if its handler happens to be * cond waiting on an internal debugger monitor, etc. * * Leave suspend status untouched since it is not possible * to distinguish debugger suspends from app suspends. */ *pstatus = JDWP_THREAD_STATUS(RUNNING); } } debugMonitorExit(threadLock); return error; } jvmtiError threadControl_interrupt(jthread thread) { ThreadNode *node; jvmtiError error; error = JVMTI_ERROR_NONE; log_debugee_location("threadControl_interrupt()", thread, NULL, 0); debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if ((node == NULL) || !HANDLING_EVENT(node)) { error = JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread) (gdata->jvmti, thread); } else { /* * Hold any interrupts until after the event is processed. */ node->pendingInterrupt = JNI_TRUE; } debugMonitorExit(threadLock); return error; } void threadControl_clearCLEInfo(JNIEnv *env, jthread thread) { ThreadNode *node; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL) { node->cleInfo.ei = 0; if (node->cleInfo.clazz != NULL) { tossGlobalRef(env, &(node->cleInfo.clazz)); } } debugMonitorExit(threadLock); } jboolean threadControl_cmpCLEInfo(JNIEnv *env, jthread thread, jclass clazz, jmethodID method, jlocation location) { ThreadNode *node; jboolean result; result = JNI_FALSE; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL && node->cleInfo.ei != 0 && node->cleInfo.method == method && node->cleInfo.location == location && (isSameObject(env, node->cleInfo.clazz, clazz))) { result = JNI_TRUE; /* we have a match */ } debugMonitorExit(threadLock); return result; } void threadControl_saveCLEInfo(JNIEnv *env, jthread thread, EventIndex ei, jclass clazz, jmethodID method, jlocation location) { ThreadNode *node; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL) { node->cleInfo.ei = ei; /* Create a class ref that will live beyond */ /* the end of this call */ saveGlobalRef(env, clazz, &(node->cleInfo.clazz)); /* if returned clazz is NULL, we just won't match */ node->cleInfo.method = method; node->cleInfo.location = location; } debugMonitorExit(threadLock); } void threadControl_setPendingInterrupt(jthread thread) { ThreadNode *node; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL) { node->pendingInterrupt = JNI_TRUE; } debugMonitorExit(threadLock); } jvmtiError threadControl_stop(jthread thread, jobject throwable) { ThreadNode *node; jvmtiError error; error = JVMTI_ERROR_NONE; log_debugee_location("threadControl_stop()", thread, NULL, 0); debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if ((node == NULL) || !HANDLING_EVENT(node)) { error = JVMTI_FUNC_PTR(gdata->jvmti,StopThread) (gdata->jvmti, thread, throwable); } else { JNIEnv *env; /* * Hold any stops until after the event is processed. */ env = getEnv(); saveGlobalRef(env, throwable, &(node->pendingStop)); } debugMonitorExit(threadLock); return error; } static jvmtiError detachHelper(JNIEnv *env, ThreadNode *node, void *arg) { invoker_detach(&node->currentInvoke); return JVMTI_ERROR_NONE; } void threadControl_detachInvokes(void) { JNIEnv *env; env = getEnv(); invoker_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); (void)enumerateOverThreadList(env, &runningThreads, detachHelper, NULL); debugMonitorExit(threadLock); invoker_unlock(); } static jvmtiError resetHelper(JNIEnv *env, ThreadNode *node, void *arg) { if (node->toBeResumed) { LOG_MISC(("thread=%p resumed", node->thread)); (void)JVMTI_FUNC_PTR(gdata->jvmti,ResumeThread)(gdata->jvmti, node->thread); node->frameGeneration++; /* Increment on each resume */ } stepControl_clearRequest(node->thread, &node->currentStep); node->toBeResumed = JNI_FALSE; node->suspendCount = 0; node->suspendOnStart = JNI_FALSE; return JVMTI_ERROR_NONE; } void threadControl_reset(void) { JNIEnv *env; env = getEnv(); eventHandler_lock(); /* for proper lock order */ debugMonitorEnter(threadLock); (void)enumerateOverThreadList(env, &runningThreads, resetHelper, NULL); (void)enumerateOverThreadList(env, &otherThreads, resetHelper, NULL); removeResumed(env, &otherThreads); freeDeferredEventModes(env); suspendAllCount = 0; /* Everything should have been resumed */ JDI_ASSERT(otherThreads.first == NULL); debugMonitorExit(threadLock); eventHandler_unlock(); } jvmtiEventMode threadControl_getInstructionStepMode(jthread thread) { ThreadNode *node; jvmtiEventMode mode; mode = JVMTI_DISABLE; debugMonitorEnter(threadLock); node = findThread(&runningThreads, thread); if (node != NULL) { mode = node->instructionStepMode; } debugMonitorExit(threadLock); return mode; } jvmtiError threadControl_setEventMode(jvmtiEventMode mode, EventIndex ei, jthread thread) { jvmtiError error; /* Global event */ if ( thread == NULL ) { error = JVMTI_FUNC_PTR(gdata->jvmti,SetEventNotificationMode) (gdata->jvmti, mode, eventIndex2jvmti(ei), thread); } else { /* Thread event */ ThreadNode *node; debugMonitorEnter(threadLock); { node = findThread(&runningThreads, thread); if ((node == NULL) || (!node->isStarted)) { JNIEnv *env; env = getEnv(); error = addDeferredEventMode(env, mode, ei, thread); } else { error = threadSetEventNotificationMode(node, mode, ei, thread); } } debugMonitorExit(threadLock); } return error; } /* * Returns the current thread, if the thread has generated at least * one event, and has not generated a thread end event. */ jthread threadControl_currentThread(void) { jthread thread; debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(&runningThreads, NULL); thread = (node == NULL) ? NULL : node->thread; } debugMonitorExit(threadLock); return thread; } jlong threadControl_getFrameGeneration(jthread thread) { jlong frameGeneration = -1; debugMonitorEnter(threadLock); { ThreadNode *node; node = findThread(NULL, thread); if (node != NULL) { frameGeneration = node->frameGeneration; } } debugMonitorExit(threadLock); return frameGeneration; }