| /* | |
| * Copyright (c) 1998, 2024, 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. | |
| * | |
| * See comment above deferEventReport() for an explanation of co-located events. | |
| */ | |
| 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. | |
| */ | |
| typedef struct ThreadNode { | |
| jthread thread; | |
| unsigned int toBeResumed : 1; /* true if this thread was successfully suspended. */ | |
| unsigned int pendingInterrupt : 1; /* true if thread is interrupted while handling an event. */ | |
| unsigned int isDebugThread : 1; /* true if this is one of our debug agent threads. */ | |
| unsigned int suspendOnStart : 1; /* true for new threads if we are currently in a VM.suspend(). */ | |
| unsigned int isStarted : 1; /* THREAD_START or VIRTUAL_THREAD_START event received. */ | |
| unsigned int is_vthread : 1; | |
| unsigned int popFrameEvent : 1; | |
| unsigned int popFrameProceed : 1; | |
| unsigned int popFrameThread : 1; | |
| EventIndex current_ei; /* Used to determine if we are currently handling an event on this thread. */ | |
| jobject pendingStop; /* Object we are throwing to stop the thread (ThreadReferenceImpl.stop). */ | |
| jint suspendCount; /* Number of outstanding suspends from the debugger. */ | |
| jvmtiEventMode instructionStepMode; | |
| StepRequest currentStep; | |
| InvokeRequest currentInvoke; | |
| struct bag *eventBag; /* Accumulation of JDWP events to be sent as a reply. */ | |
| CoLocatedEventInfo cleInfo; /* See comment above deferEventReport() for an explanation. */ | |
| struct ThreadNode *next; | |
| struct ThreadNode *prev; | |
| jlong frameGeneration; /* used to generate a unique frameID. Incremented whenever existing frameID | |
| needs to be invalidated, such as when the thread is resumed. */ | |
| struct ThreadList *list; /* Tells us what list this thread is in. */ | |
| #ifdef DEBUG_THREADNAME | |
| char name[256]; | |
| #endif | |
| } 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 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; | |
| static ThreadList runningVThreads; /* VThreads we are tracking (not necessarily all vthreads). */ | |
| static jint numRunningVThreads; | |
| #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; | |
| /* 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 && node == NULL) { | |
| /* Just return. This can happen when clearing the TLS. */ | |
| 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 and vthreads. | |
| * All assume that the threadLock is held before calling. | |
| */ | |
| /* | |
| * Search for a thread on the list. If list==NULL, search all lists. | |
| */ | |
| static ThreadNode * | |
| findThread(ThreadList *list, jthread thread) | |
| { | |
| ThreadNode *node; | |
| /* Get thread local storage for quick thread -> node access */ | |
| node = getThreadLocalStorage(thread); | |
| if ( node == NULL ) { | |
| /* | |
| * If the thread was not yet started when the ThreadNode was created, then it | |
| * got added to the otherThreads list and its thread local storage was not set. | |
| * Search for it in the otherThreads list. | |
| */ | |
| if ( list == NULL || list == &otherThreads ) { | |
| node = nonTlsSearch(getEnv(), &otherThreads, thread); | |
| } | |
| /* | |
| * Normally we can assume that a thread with no TLS will never be in the runningThreads | |
| * list. This is because we always set the TLS when adding to runningThreads. | |
| * However, when a thread exits, its TLS is automatically cleared. Normally this | |
| * is not a problem because the debug agent will first get a THREAD_END event, | |
| * and that will cause the thread to be removed from runningThreads, thus we | |
| * avoid this situation of having a thread in runningThreads, but with no TLS. | |
| * | |
| * However... there is one exception to this. While handling VM_DEATH, the first thing | |
| * the debug agent does is clear all the callbacks. This means we will no longer | |
| * get THREAD_END events as threads exit. This means we might find threads on | |
| * runningThreads with no TLS during VM_DEATH. Essentially the THREAD_END that | |
| * would normally have resulted in removing the thread from runningThreads is | |
| * missed, so the thread remains on runningThreads. | |
| * | |
| * The end result of all this is that if the TLS lookup failed, we still need to check | |
| * if the thread is on runningThreads, but only if JVMTI callbacks have been cleared. | |
| * Otherwise the thread should not be on the runningThreads. | |
| */ | |
| if ( !gdata->jvmtiCallBacksCleared ) { | |
| /* The thread better not be on either list if the TLS lookup failed. */ | |
| JDI_ASSERT(!nonTlsSearch(getEnv(), &runningThreads, thread)); | |
| JDI_ASSERT(!nonTlsSearch(getEnv(), &runningVThreads, thread)); | |
| } else { | |
| /* | |
| * Search the runningThreads and runningVThreads lists. The TLS lookup may have | |
| * failed because the thread has terminated, but we never got the THREAD_END event. | |
| * The big comment above explains why this can happen. | |
| */ | |
| if ( node == NULL ) { | |
| if ( list == NULL || list == &runningThreads ) { | |
| node = nonTlsSearch(getEnv(), &runningThreads, thread); | |
| } | |
| if ( node == NULL ) { | |
| if ( list == NULL || list == &runningVThreads ) { | |
| node = nonTlsSearch(getEnv(), &runningVThreads, thread); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /* If a list is supplied, only return ones in this list */ | |
| if ( node != NULL && list != NULL && node->list != list ) { | |
| return NULL; | |
| } | |
| return node; | |
| } | |
| /* Search for a running thread, including vthreads. */ | |
| static ThreadNode * | |
| findRunningThread(jthread thread) | |
| { | |
| ThreadNode *node; | |
| if (isVThread(thread)) { | |
| node = findThread(&runningVThreads, thread); | |
| } else { | |
| node = findThread(&runningThreads, thread); | |
| } | |
| return node; | |
| } | |
| /* Remove a ThreadNode from a ThreadList */ | |
| static void | |
| removeNode(ThreadNode *node) | |
| { | |
| ThreadNode *prev; | |
| ThreadNode *next; | |
| ThreadList *list; | |
| prev = node->prev; | |
| next = node->next; | |
| list = node->list; | |
| 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; | |
| if (list == &runningVThreads) { | |
| numRunningVThreads--; | |
| } | |
| } | |
| /* 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; | |
| if (list == &runningVThreads) { | |
| numRunningVThreads++; | |
| } | |
| } | |
| static ThreadNode * | |
| insertThread(JNIEnv *env, ThreadList *list, jthread thread) | |
| { | |
| ThreadNode *node; | |
| struct bag *eventBag; | |
| jboolean is_vthread = (list == &runningVThreads); | |
| 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; | |
| } | |
| if (!is_vthread) { | |
| if (threadControl_isDebugThread(node->thread)) { | |
| /* Remember if it is a debug 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; | |
| } | |
| } | |
| } else { /* vthread */ | |
| jint vthread_state = 0; | |
| jvmtiError error = threadState(node->thread, &vthread_state); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "getting vthread state"); | |
| } | |
| if ((vthread_state & JVMTI_THREAD_STATE_ALIVE) == 0) { | |
| // Thread not alive so put on otherThreads list instead of runningVThreads. | |
| // It might not have started yet or might have terminated. Either way, | |
| // otherThreads is the place for it. | |
| list = &otherThreads; | |
| } | |
| if (suspendAllCount > 0) { | |
| // Assume the suspendAllCount, just like the regular thread case above. | |
| node->suspendCount = suspendAllCount; | |
| if (vthread_state == 0) { | |
| // If state == 0, then this is a new vthread that has not been started yet. | |
| // Need to suspendOnStart in that case, just like the regular thread case above. | |
| node->suspendOnStart = JNI_TRUE; | |
| } | |
| } | |
| if (vthread_state != 0) { | |
| // This is an already started vthread that we were not already tracking. | |
| node->isStarted = JNI_TRUE; | |
| } | |
| } | |
| node->current_ei = 0; | |
| node->is_vthread = is_vthread; | |
| node->instructionStepMode = JVMTI_DISABLE; | |
| node->eventBag = eventBag; | |
| addNode(list, node); | |
| #ifdef DEBUG_THREADNAME | |
| { | |
| /* Set the thread name */ | |
| jvmtiThreadInfo info; | |
| jvmtiError error; | |
| memset(&info, 0, sizeof(info)); | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo) | |
| (gdata->jvmti, node->thread, &info); | |
| if (info.name != NULL) { | |
| strncpy(node->name, info.name, sizeof(node->name) - 1); | |
| jvmtiDeallocate(info.name); | |
| } | |
| } | |
| #endif | |
| /* Set thread local storage for quick thread -> node access. | |
| * Threads that are not yet started do not allow setting of TLS. These | |
| * threads go on the otherThreads list and have their TLS set | |
| * when moved to the runningThreads list. findThread() knows to look | |
| * on otherThreads when the TLS lookup fails. | |
| */ | |
| if (list != &otherThreads) { | |
| 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, ThreadNode *node) | |
| { | |
| JDI_ASSERT(node != NULL); | |
| removeNode(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, node); | |
| } | |
| node = temp; | |
| } | |
| } | |
| static void | |
| removeVThreads(JNIEnv *env) | |
| { | |
| ThreadList *list = &runningVThreads; | |
| ThreadNode *node = list->first; | |
| while (node != NULL) { | |
| ThreadNode *temp = node->next; | |
| removeNode(node); | |
| clearThread(env, node); | |
| node = temp; | |
| } | |
| } | |
| static void | |
| moveNode(ThreadList *source, ThreadList *dest, ThreadNode *node) | |
| { | |
| removeNode(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. | |
| * | |
| * stepControl_beginStep() grabs the eventHandler lock and stepControl lock | |
| * before eventually ending up here, so we need to maintain that order here. | |
| * Similarly, invoker_completeInvokeRequest() grabs the eventHandler lock | |
| * and invoker lock. | |
| */ | |
| callback_lock(); | |
| eventHandler_lock(); | |
| stepControl_lock(); | |
| invoker_lock(); | |
| eventHelper_lock(); | |
| debugMonitorEnter(threadLock); | |
| commonRef_lock(); | |
| } | |
| static void | |
| releaseLocks(void) | |
| { | |
| commonRef_unlock(); | |
| debugMonitorExit(threadLock); | |
| eventHelper_unlock(); | |
| invoker_unlock(); | |
| stepControl_unlock(); | |
| eventHandler_unlock(); | |
| callback_unlock(); | |
| } | |
| void | |
| threadControl_initialize(void) | |
| { | |
| suspendAllCount = 0; | |
| runningThreads.first = NULL; | |
| otherThreads.first = NULL; | |
| runningVThreads.first = NULL; | |
| debugThreadCount = 0; | |
| threadLock = debugMonitorCreate("JDWP Thread Lock"); | |
| } | |
| void | |
| threadControl_onConnect(void) | |
| { | |
| } | |
| void | |
| threadControl_onDisconnect(void) | |
| { | |
| } | |
| 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; | |
| } | |
| jvmtiDeallocate(threads); | |
| } | |
| } 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; | |
| } | |
| /* | |
| * JVMTI_ERROR_THREAD_SUSPENDED used to be possible when Thread.suspend() | |
| * was still supported, but now we should no longer ever see it. | |
| */ | |
| JDI_ASSERT(error != JVMTI_ERROR_THREAD_SUSPENDED); | |
| 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) { | |
| // We should never see both toBeResumed and suspendOnStart set true. | |
| JDI_ASSERT(!node->suspendOnStart); | |
| LOG_MISC(("thread=%p resumed", node->thread)); | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThread) | |
| (gdata->jvmti, node->thread); | |
| 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; | |
| } | |
| } | |
| // TODO - vthread node cleanup: If this is a vthread and suspendCount == 0, | |
| // we should delete the node. | |
| } | |
| 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 */ | |
| } | |
| 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; | |
| node = findRunningThread(thread); | |
| if (node == NULL) { | |
| if (isVThread(thread)) { | |
| /* | |
| * Since we don't track all vthreads, it might not be in the list already. Start | |
| * tracking it now. | |
| */ | |
| node = insertThread(env, &runningVThreads, thread); | |
| } else { | |
| /* | |
| * 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 = insertThread(env, &otherThreads, thread); | |
| } | |
| } | |
| #if 0 | |
| tty_message("commonSuspend: node(%p) suspendCount(%d) %s", node, node->suspendCount, node->name); | |
| #endif | |
| 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. | |
| */ | |
| if (node->suspendCount == 1 && node->suspendOnStart) { | |
| // We should never see both toBeResumed and suspendOnStart set true. | |
| JDI_ASSERT(!node->toBeResumed); | |
| node->suspendCount--; | |
| // TODO - vthread node cleanup: If this is a vthread, we should delete the node. | |
| return JVMTI_ERROR_NONE; | |
| } | |
| 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 | |
| * JVMTI SuspendThread() or JVMTI SuspendThreadList() was called | |
| * on this thread. | |
| */ | |
| if (node->suspendCount == 1 && node->toBeResumed) { | |
| // We should never see both toBeResumed and suspendOnStart set true. | |
| JDI_ASSERT(!node->suspendOnStart); | |
| jthread **listPtr = (jthread **)arg; | |
| **listPtr = node->thread; | |
| (*listPtr)++; | |
| } | |
| 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 | |
| * JVMTI SuspendThread() or JVMTDI SuspendThreadList() was called | |
| * on this thread. | |
| */ | |
| if (node->suspendCount == 1 && node->toBeResumed) { | |
| // We should never see both toBeResumed and suspendOnStart set true. | |
| JDI_ASSERT(!node->suspendOnStart); | |
| jint *counter = (jint *)arg; | |
| (*counter)++; | |
| } | |
| return JVMTI_ERROR_NONE; | |
| } | |
| 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); | |
| (void) enumerateOverThreadList(env, &runningVThreads, resumeCountHelper, | |
| &reqCnt); | |
| if (reqCnt == 0) { | |
| /* nothing to hard resume so do just the accounting part */ | |
| (void) enumerateOverThreadList(env, &runningThreads, resumeCopyHelper, | |
| NULL); | |
| (void) enumerateOverThreadList(env, &runningVThreads, 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); | |
| (void) enumerateOverThreadList(env, &runningVThreads, resumeCopyHelper, | |
| &reqPtr); | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeThreadList) | |
| (gdata->jvmti, reqCnt, reqList, results); | |
| for (i = 0; i < reqCnt; i++) { | |
| ThreadNode *node; | |
| node = findRunningThread(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 JVMTI 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 */ | |
| // TODO - vthread node cleanup: If this is a vthread, we should delete the node. | |
| } | |
| 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 0 | |
| tty_message("commonResume: node(%p) suspendCount(%d) %s", node, node->suspendCount, node->name); | |
| #endif | |
| /* | |
| * 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 = findRunningThread(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, unless it is a vthread. | |
| */ | |
| if (isVThread(thread)) { | |
| jint vthread_state = 0; | |
| jvmtiError error = threadState(thread, &vthread_state); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "getting vthread state"); | |
| } | |
| if (vthread_state == 0) { | |
| // If state == 0, then this is a new vthread that has not been started yet. | |
| *count = 0; | |
| } else { | |
| // This is a started vthread that we are not tracking. Use suspendAllCount. | |
| *count = suspendAllCount; | |
| } | |
| } else { | |
| *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; | |
| } | |
| static jvmtiError | |
| incrementSuspendCountHelper(JNIEnv *env, ThreadNode *node, void *arg) | |
| { | |
| node->toBeResumed = JNI_TRUE; | |
| node->suspendCount++; | |
| return JVMTI_ERROR_NONE; | |
| } | |
| 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; | |
| #if 0 | |
| tty_message("threadControl_suspendAll: suspendAllCount(%d)", suspendAllCount); | |
| #endif | |
| 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; | |
| if (gdata->vthreadsSupported) { | |
| /* Tell JVMTI to suspend all virtual threads. */ | |
| if (suspendAllCount == 0) { | |
| error = JVMTI_FUNC_PTR(gdata->jvmti, SuspendAllVirtualThreads) | |
| (gdata->jvmti, 0, NULL); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "cannot suspend all virtual threads"); | |
| } | |
| // We need a notify here just like we do any time we suspend a thread. | |
| // See commonSuspendList() and suspendThreadByNode(). | |
| debugMonitorNotifyAll(threadLock); | |
| } | |
| /* | |
| * Increment suspendCount of each virtual thread that we are tracking. Note the | |
| * complement to this that happens during the resumeAll() is handled by | |
| * commonResumeList(), so it's a bit orthogonal to how we handle incrementing | |
| * the suspendCount. | |
| */ | |
| error = enumerateOverThreadList(env, &runningVThreads, incrementSuspendCountHelper, NULL); | |
| JDI_ASSERT(error == JVMTI_ERROR_NONE); | |
| } | |
| threads = allThreads(&count); | |
| if (threads == NULL) { | |
| error = AGENT_ERROR_OUT_OF_MEMORY; | |
| goto err; | |
| } | |
| error = commonSuspendList(env, count, threads); | |
| 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) { | |
| /* | |
| * Pin all objects to prevent objects from being | |
| * garbage collected while the VM is suspended. | |
| */ | |
| commonRef_pinAll(); | |
| suspendAllCount++; | |
| } | |
| err: | |
| jvmtiDeallocate(threads); | |
| } 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); | |
| } | |
| static jvmtiError | |
| excludeCountHelper(JNIEnv *env, ThreadNode *node, void *arg) | |
| { | |
| JDI_ASSERT(node->is_vthread); | |
| if (node->suspendCount > 0) { | |
| jint *counter = (jint *)arg; | |
| (*counter)++; | |
| } | |
| return JVMTI_ERROR_NONE; | |
| } | |
| static jvmtiError | |
| excludeCopyHelper(JNIEnv *env, ThreadNode *node, void *arg) | |
| { | |
| JDI_ASSERT(node->is_vthread); | |
| if (node->suspendCount > 0) { | |
| jthread **listPtr = (jthread **)arg; | |
| **listPtr = node->thread; | |
| (*listPtr)++; | |
| } | |
| return JVMTI_ERROR_NONE; | |
| } | |
| jvmtiError | |
| threadControl_resumeAll(void) | |
| { | |
| jvmtiError error; | |
| JNIEnv *env; | |
| #if 0 | |
| tty_message("threadControl_resumeAll: suspendAllCount(%d)", suspendAllCount); | |
| #endif | |
| env = getEnv(); | |
| log_debugee_location("threadControl_resumeAll()", NULL, NULL, 0); | |
| eventHandler_lock(); /* for proper lock order */ | |
| debugMonitorEnter(threadLock); | |
| if (gdata->vthreadsSupported) { | |
| if (suspendAllCount == 1) { | |
| jint excludeCnt = 0; | |
| jthread *excludeList = NULL; | |
| /* | |
| * Tell JVMTI to resume all virtual threads except for those we | |
| * are tracking separately. The commonResumeList() call below will | |
| * resume any vthread with a suspendCount == 1, and we want to ignore | |
| * vthreads with a suspendCount > 0. Therefore we don't want | |
| * ResumeAllVirtualThreads resuming these vthreads. We must first | |
| * build a list of them to pass as the exclude list. | |
| */ | |
| enumerateOverThreadList(env, &runningVThreads, excludeCountHelper, | |
| &excludeCnt); | |
| if (excludeCnt > 0) { | |
| excludeList = newArray(excludeCnt, sizeof(jthread)); | |
| if (excludeList == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"exclude list"); | |
| } | |
| { | |
| jthread *excludeListPtr = excludeList; | |
| enumerateOverThreadList(env, &runningVThreads, excludeCopyHelper, | |
| &excludeListPtr); | |
| } | |
| } | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeAllVirtualThreads) | |
| (gdata->jvmti, excludeCnt, excludeList); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "cannot resume all virtual threads"); | |
| } | |
| // We need a notify here just like we do any time we resume a thread. | |
| // See commonResumeList() and resumeThreadByNode(). | |
| debugMonitorNotifyAll(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). | |
| */ | |
| error = commonResumeList(env); | |
| if ((error == JVMTI_ERROR_NONE) && (otherThreads.first != NULL)) { | |
| error = enumerateOverThreadList(env, &otherThreads, | |
| resumeHelper, NULL); | |
| removeResumed(env, &otherThreads); | |
| } | |
| if (suspendAllCount > 0) { | |
| /* | |
| * Unpin all objects. | |
| */ | |
| commonRef_unpinAll(); | |
| 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 = findRunningThread(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 = findRunningThread(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_VIRTUAL_THREAD_START: | |
| case EI_VIRTUAL_THREAD_END: | |
| JDI_ASSERT(JNI_FALSE); | |
| 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, EventInfo *evinfo, jobject currentException) | |
| { | |
| ThreadNode *node; | |
| JNIEnv *env; | |
| struct bag *eventBag; | |
| jthread threadToSuspend; | |
| jboolean consumed; | |
| EventIndex ei = evinfo->ei; | |
| jthread thread = evinfo->thread; | |
| 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, (node->is_vthread ? &runningVThreads : &runningThreads), node); | |
| /* Now that we know the thread has started, we can set its TLS.*/ | |
| setThreadLocalStorage(thread, (void*)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. | |
| */ | |
| if (evinfo->is_vthread) { | |
| node = insertThread(env, &runningVThreads, thread); | |
| } else { | |
| node = insertThread(env, &runningThreads, thread); | |
| } | |
| } | |
| JDI_ASSERT(ei != EI_VIRTUAL_THREAD_START); // was converted to EI_THREAD_START | |
| JDI_ASSERT(ei != EI_VIRTUAL_THREAD_END); // was converted to EI_THREAD_END | |
| if (ei == EI_THREAD_START) { | |
| node->isStarted = JNI_TRUE; | |
| processDeferredEventModes(env, thread, node); | |
| } | |
| if (ei == EI_THREAD_END) { | |
| // If the node was previously freed, then it was just recreated and we need | |
| // to mark it as started. | |
| node->isStarted = JNI_TRUE; | |
| } | |
| 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, jthread thread, int pendingInterrupt, jobject pendingStop) | |
| { | |
| /* | |
| * Take care of any pending interrupts/stops. | |
| */ | |
| if (pendingInterrupt) { | |
| JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread) | |
| (gdata->jvmti, thread); | |
| /* | |
| * TO DO: Log error | |
| */ | |
| } | |
| if (pendingStop != NULL) { | |
| JVMTI_FUNC_PTR(gdata->jvmti,StopThread) | |
| (gdata->jvmti, thread, pendingStop); | |
| /* | |
| * TO DO: Log error | |
| */ | |
| } | |
| } | |
| void | |
| threadControl_onEventHandlerExit(EventIndex ei, jthread thread, | |
| struct bag *eventBag, jobject currentException) | |
| { | |
| ThreadNode *node; | |
| JNIEnv *env = getEnv(); | |
| log_debugee_location("threadControl_onEventHandlerExit()", thread, NULL, 0); | |
| if (ei == EI_THREAD_END) { | |
| eventHandler_lock(); /* for proper lock order - see removeThread() call below */ | |
| debugMonitorEnter(threadLock); | |
| node = findRunningThread(thread); | |
| if (node == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted"); | |
| } | |
| removeThread(env, node); // Grabs handlerLock, thus the reason for first grabbing it above. | |
| node = NULL; // We exiting the threadLock. No longer safe to access. | |
| debugMonitorExit(threadLock); | |
| eventHandler_unlock(); | |
| } else { | |
| debugMonitorEnter(threadLock); | |
| node = findRunningThread(thread); | |
| if (node == NULL) { | |
| EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted"); | |
| } | |
| /* No need to do the following if the thread is about to die.*/ | |
| int pendingInterrupt = node->pendingInterrupt; | |
| jobject pendingStop = node->pendingStop; | |
| jthread thread = node->thread; | |
| node->pendingInterrupt = JNI_FALSE; | |
| node->pendingStop = NULL; | |
| node->eventBag = eventBag; | |
| node->current_ei = 0; | |
| node = NULL; // We exiting the threadLock. No longer safe to access. | |
| // doPendingTasks() may do an upcall to java, and we don't want to hold any | |
| // locks when doing that. Thus we got all our node updates done first | |
| // and can now exit the threadLock. | |
| debugMonitorExit(threadLock); | |
| if (currentException != NULL) { | |
| // We need to rethrow the exception that was current when we received the | |
| // JVMTI event. If there is a pending async exception, StopThread will be | |
| // called from doPendingTasks() immediately below. Depending on the VM | |
| // implementation and state, the async exception might immediately overwrite | |
| // the currentException, or it might be delayed until later. | |
| // | |
| // Note in order the keep the JNI Checker happy, we had to delay doing this | |
| // until now. Otherwise there are complaints when JNI IsVirtualThread is called. | |
| JNI_FUNC_PTR(env,Throw)(env, currentException); | |
| } | |
| doPendingTasks(env, thread, pendingInterrupt, pendingStop); | |
| if (pendingStop != NULL) { | |
| tossGlobalRef(env, &pendingStop); | |
| } | |
| } | |
| } | |
| /* 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 = findRunningThread(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) | |
| { | |
| log_debugee_location("threadControl_interrupt()", thread, NULL, 0); | |
| return JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread)(gdata->jvmti, thread); | |
| } | |
| void | |
| threadControl_clearCLEInfo(JNIEnv *env, jthread thread) | |
| { | |
| ThreadNode *node; | |
| debugMonitorEnter(threadLock); | |
| node = findRunningThread(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 = findRunningThread(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 = findRunningThread(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); | |
| } | |
| /* | |
| * Support for getting an interrupt in an application thread while doing | |
| * a debugMonitorWait(). | |
| */ | |
| void | |
| threadControl_setPendingInterrupt(jthread thread) | |
| { | |
| ThreadNode *node; | |
| debugMonitorEnter(threadLock); | |
| node = findRunningThread(thread); | |
| JDI_ASSERT(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); | |
| if (gdata->vthreadsSupported) { | |
| if (suspendAllCount > 0) { | |
| /* Tell JVMTI to resume all virtual threads. */ | |
| jvmtiError error = JVMTI_FUNC_PTR(gdata->jvmti,ResumeAllVirtualThreads) | |
| (gdata->jvmti, 0, NULL); | |
| if (error != JVMTI_ERROR_NONE) { | |
| EXIT_ERROR(error, "cannot resume all virtual threads"); | |
| } | |
| } | |
| } | |
| (void)enumerateOverThreadList(env, &runningThreads, resetHelper, NULL); | |
| (void)enumerateOverThreadList(env, &otherThreads, resetHelper, NULL); | |
| (void)enumerateOverThreadList(env, &runningVThreads, resetHelper, NULL); | |
| removeResumed(env, &otherThreads); | |
| freeDeferredEventModes(env); | |
| suspendAllCount = 0; | |
| /* Everything should have been resumed */ | |
| JDI_ASSERT(otherThreads.first == NULL); | |
| /* Threads could be waiting in blockOnDebuggerSuspend */ | |
| debugMonitorNotifyAll(threadLock); | |
| debugMonitorExit(threadLock); | |
| eventHandler_unlock(); | |
| /* | |
| * Unless we are remembering all vthreads when the debugger is not connected, | |
| * we free them all up here. | |
| */ | |
| if (!gdata->rememberVThreadsWhenDisconnected) { | |
| /* | |
| * First we need to wait for all active callbacks to complete. They were resumed | |
| * above by the resetHelper. We can't remove the vthreads until after they complete, | |
| * because the vthread ThreadNodes might be referenced as the callbacks unwind. | |
| * We do this outside of any locking, because the callbacks may need to acquire locks | |
| * in order to complete. It's ok if there are more callbacks after this point because | |
| * the only callbacks enabled are the permanent ones, and they never involve vthreads. | |
| */ | |
| eventHandler_waitForActiveCallbacks(); | |
| /* | |
| * Now that event callbacks have exited, we can reacquire the threadLock, which | |
| * is needed before before calling removeVThreads(). | |
| */ | |
| debugMonitorEnter(threadLock); | |
| removeVThreads(env); | |
| debugMonitorExit(threadLock); | |
| } | |
| } | |
| jvmtiEventMode | |
| threadControl_getInstructionStepMode(jthread thread) | |
| { | |
| ThreadNode *node; | |
| jvmtiEventMode mode; | |
| mode = JVMTI_DISABLE; | |
| debugMonitorEnter(threadLock); | |
| node = findRunningThread(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 = findRunningThread(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. | |
| */ | |
| jthread | |
| threadControl_currentThread(void) | |
| { | |
| jthread thread = NULL; | |
| jvmtiError error = JVMTI_FUNC_PTR(gdata->jvmti,GetCurrentThread)(gdata->jvmti, &thread); | |
| 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; | |
| } | |
| jthread * | |
| threadControl_allVThreads(jint *numVThreads) | |
| { | |
| JNIEnv *env; | |
| ThreadNode *node; | |
| jthread* vthreads; | |
| env = getEnv(); | |
| debugMonitorEnter(threadLock); | |
| *numVThreads = numRunningVThreads; | |
| if (gdata->assertOn) { | |
| /* Count the number of vthreads just to make sure we are tracking the count properly. */ | |
| jint countedVThreads = 0; | |
| for (node = runningVThreads.first; node != NULL; node = node->next) { | |
| countedVThreads++; | |
| } | |
| JDI_ASSERT(countedVThreads == numRunningVThreads); | |
| } | |
| /* Allocate and fill in the vthreads array. */ | |
| vthreads = jvmtiAllocate(numRunningVThreads * sizeof(jthread*)); | |
| if (vthreads != NULL) { | |
| int i = 0; | |
| for (node = runningVThreads.first; node != NULL; node = node->next) { | |
| vthreads[i++] = node->thread; | |
| } | |
| } | |
| debugMonitorExit(threadLock); | |
| return vthreads; | |
| } | |
| /***** APIs for debugging the debug agent *****/ | |
| static void dumpThreadList(ThreadList *list); | |
| static void dumpThread(ThreadNode *node); | |
| void | |
| threadControl_dumpAllThreads() | |
| { | |
| tty_message("suspendAllCount: %d", suspendAllCount); | |
| tty_message("Dumping runningThreads:"); | |
| dumpThreadList(&runningThreads); | |
| tty_message("\nDumping runningVThreads:"); | |
| dumpThreadList(&runningVThreads); | |
| tty_message("\nDumping otherThreads:"); | |
| dumpThreadList(&otherThreads); | |
| } | |
| void | |
| threadControl_dumpThread(jthread thread) | |
| { | |
| ThreadNode* node = findThread(NULL, thread); | |
| if (node == NULL) { | |
| tty_message("Thread not found"); | |
| } else { | |
| dumpThread(node); | |
| } | |
| } | |
| static void | |
| dumpThreadList(ThreadList *list) | |
| { | |
| ThreadNode *node; | |
| for (node = list->first; node != NULL; node = node->next) { | |
| if (!node->isDebugThread) { | |
| dumpThread(node); | |
| } | |
| } | |
| } | |
| #ifdef DEBUG_THREADNAME | |
| static void | |
| setThreadName(ThreadNode *node) | |
| { | |
| /* | |
| * Sometimes the debuggee changes the thread name, so we need to fetch | |
| * and set it again. | |
| */ | |
| jvmtiThreadInfo info; | |
| jvmtiError error; | |
| memset(&info, 0, sizeof(info)); | |
| error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadInfo) | |
| (gdata->jvmti, node->thread, &info); | |
| if (info.name != NULL) { | |
| strncpy(node->name, info.name, sizeof(node->name) - 1); | |
| jvmtiDeallocate(info.name); | |
| } | |
| } | |
| #endif | |
| #if 0 | |
| static jint | |
| getThreadState(ThreadNode *node) | |
| { | |
| jint state = 0; | |
| jvmtiError error = FUNC_PTR(gdata->jvmti,GetThreadState) | |
| (gdata->jvmti, node->thread, &state); | |
| return state; | |
| } | |
| #endif | |
| static void | |
| dumpThread(ThreadNode *node) { | |
| tty_message("Thread: node = %p, jthread = %p", node, node->thread); | |
| #ifdef DEBUG_THREADNAME | |
| setThreadName(node); | |
| tty_message("\tname: %s", node->name); | |
| #endif | |
| // More fields can be printed here when needed. The amount of output is intentionally | |
| // kept small so it doesn't generate too much output. | |
| tty_message("\tsuspendCount: %d", node->suspendCount); | |
| #if 0 | |
| tty_message("\tsuspendAllCount: %d", suspendAllCount); | |
| tty_message("\tthreadState: 0x%x", getThreadState(node)); | |
| tty_message("\ttoBeResumed: %d", node->toBeResumed); | |
| tty_message("\tis_vthread: %d", node->is_vthread); | |
| tty_message("\tpendingInterrupt: %d", node->pendingInterrupt); | |
| tty_message("\tcurrentInvoke.pending: %d", node->currentInvoke.pending); | |
| tty_message("\tcurrentInvoke.started: %d", node->currentInvoke.started); | |
| tty_message("\tcurrentInvoke.available: %d", node->currentInvoke.available); | |
| tty_message("\tobjID: %d", commonRef_refToID(getEnv(), node->thread)); | |
| #endif | |
| } |