--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/back/threadControl.c Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,2501 @@
+/*
+ * Copyright 1998-2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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;
+}