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