src/jdk.jdwp.agent/share/native/libjdwp/eventHelper.c
changeset 47216 71c04702a3d5
parent 46841 76a15bd1de0a
child 47525 e05aff6beada
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jdwp.agent/share/native/libjdwp/eventHelper.c	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1123 @@
+/*
+ * Copyright (c) 1998, 2017, 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 "outStream.h"
+#include "eventHandler.h"
+#include "threadControl.h"
+#include "invoker.h"
+
+/*
+ * Event helper thread command commandKinds
+ */
+#define COMMAND_REPORT_EVENT_COMPOSITE          1
+#define COMMAND_REPORT_INVOKE_DONE              2
+#define COMMAND_REPORT_VM_INIT                  3
+#define COMMAND_SUSPEND_THREAD                  4
+
+/*
+ * Event helper thread command singleKinds
+ */
+#define COMMAND_SINGLE_EVENT                    11
+#define COMMAND_SINGLE_UNLOAD                   12
+#define COMMAND_SINGLE_FRAME_EVENT              13
+
+typedef struct EventCommandSingle {
+    jbyte suspendPolicy; /* NOTE: Must be the first field */
+    jint id;
+    EventInfo info;
+} EventCommandSingle;
+
+typedef struct UnloadCommandSingle {
+    char *classSignature;
+    jint id;
+} UnloadCommandSingle;
+
+typedef struct FrameEventCommandSingle {
+    jbyte suspendPolicy; /* NOTE: Must be the first field */
+    jint id;
+    EventIndex ei;
+    jthread thread;
+    jclass clazz;
+    jmethodID method;
+    jlocation location;
+    char typeKey;         /* Not used for method entry events */
+                          /* If typeKey is 0, then no return value is needed */
+    jvalue returnValue;   /* Not used for method entry events */
+} FrameEventCommandSingle;
+
+typedef struct CommandSingle {
+    jint singleKind;
+    union {
+        EventCommandSingle eventCommand;
+        UnloadCommandSingle unloadCommand;
+        FrameEventCommandSingle frameEventCommand;
+    } u;
+} CommandSingle;
+
+typedef struct ReportInvokeDoneCommand {
+    jthread thread;
+} ReportInvokeDoneCommand;
+
+typedef struct ReportVMInitCommand {
+    jbyte suspendPolicy; /* NOTE: Must be the first field */
+    jthread thread;
+} ReportVMInitCommand;
+
+typedef struct SuspendThreadCommand {
+    jthread thread;
+} SuspendThreadCommand;
+
+typedef struct ReportEventCompositeCommand {
+    jbyte suspendPolicy; /* NOTE: Must be the first field */
+    jint eventCount;
+    CommandSingle singleCommand[1]; /* variable length */
+} ReportEventCompositeCommand;
+
+typedef struct HelperCommand {
+    jint commandKind;
+    jboolean done;
+    jboolean waiting;
+    jbyte sessionID;
+    struct HelperCommand *next;
+    union {
+        /* NOTE: Each of the structs below must have the same first field */
+        ReportEventCompositeCommand reportEventComposite;
+        ReportInvokeDoneCommand     reportInvokeDone;
+        ReportVMInitCommand         reportVMInit;
+        SuspendThreadCommand        suspendThread;
+    } u;
+    /* composite array expand out, put nothing after */
+} HelperCommand;
+
+typedef struct {
+    HelperCommand *head;
+    HelperCommand *tail;
+} CommandQueue;
+
+static CommandQueue commandQueue;
+static jrawMonitorID commandQueueLock;
+static jrawMonitorID commandCompleteLock;
+static jrawMonitorID blockCommandLoopLock;
+static jint maxQueueSize = 50 * 1024; /* TO DO: Make this configurable */
+static jboolean holdEvents;
+static jint currentQueueSize = 0;
+static jint currentSessionID;
+
+static void saveEventInfoRefs(JNIEnv *env, EventInfo *evinfo);
+static void tossEventInfoRefs(JNIEnv *env, EventInfo *evinfo);
+
+static jint
+commandSize(HelperCommand *command)
+{
+    jint size = sizeof(HelperCommand);
+    if (command->commandKind == COMMAND_REPORT_EVENT_COMPOSITE) {
+        /*
+         * One event is accounted for in the Helper Command. If there are
+         * more, add to size here.
+         */
+        /*LINTED*/
+        size += ((int)sizeof(CommandSingle) *
+                     (command->u.reportEventComposite.eventCount - 1));
+    }
+    return size;
+}
+
+static void
+freeCommand(HelperCommand *command)
+{
+    if ( command == NULL )
+        return;
+    jvmtiDeallocate(command);
+}
+
+static void
+enqueueCommand(HelperCommand *command,
+               jboolean wait, jboolean reportingVMDeath)
+{
+    static jboolean vmDeathReported = JNI_FALSE;
+    CommandQueue *queue = &commandQueue;
+    jint size = commandSize(command);
+
+    command->done = JNI_FALSE;
+    command->waiting = wait;
+    command->next = NULL;
+
+    debugMonitorEnter(commandQueueLock);
+    while (size + currentQueueSize > maxQueueSize) {
+        debugMonitorWait(commandQueueLock);
+    }
+    log_debugee_location("enqueueCommand(): HelperCommand being processed", NULL, NULL, 0);
+    if (vmDeathReported) {
+        /* send no more events after VMDeath and don't wait */
+        wait = JNI_FALSE;
+    } else {
+        currentQueueSize += size;
+
+        if (queue->head == NULL) {
+            queue->head = command;
+        } else {
+            queue->tail->next = command;
+        }
+        queue->tail = command;
+
+        if (reportingVMDeath) {
+            vmDeathReported = JNI_TRUE;
+        }
+    }
+    debugMonitorNotifyAll(commandQueueLock);
+    debugMonitorExit(commandQueueLock);
+
+    if (wait) {
+        debugMonitorEnter(commandCompleteLock);
+        while (!command->done) {
+            log_debugee_location("enqueueCommand(): HelperCommand wait", NULL, NULL, 0);
+            debugMonitorWait(commandCompleteLock);
+        }
+        freeCommand(command);
+        debugMonitorExit(commandCompleteLock);
+    }
+}
+
+static void
+completeCommand(HelperCommand *command)
+{
+    if (command->waiting) {
+        debugMonitorEnter(commandCompleteLock);
+        command->done = JNI_TRUE;
+        log_debugee_location("completeCommand(): HelperCommand done waiting", NULL, NULL, 0);
+        debugMonitorNotifyAll(commandCompleteLock);
+        debugMonitorExit(commandCompleteLock);
+    } else {
+        freeCommand(command);
+    }
+}
+
+static HelperCommand *
+dequeueCommand(void)
+{
+    HelperCommand *command = NULL;
+    CommandQueue *queue = &commandQueue;
+    jint size;
+
+    debugMonitorEnter(commandQueueLock);
+
+    while (command == NULL) {
+        while (holdEvents || (queue->head == NULL)) {
+            debugMonitorWait(commandQueueLock);
+        }
+
+        JDI_ASSERT(queue->head);
+        command = queue->head;
+        queue->head = command->next;
+        if (queue->tail == command) {
+            queue->tail = NULL;
+        }
+
+        log_debugee_location("dequeueCommand(): command being dequeued", NULL, NULL, 0);
+
+        size = commandSize(command);
+        /*
+         * Immediately close out any commands enqueued from
+         * a dead VM or a previously attached debugger.
+         */
+        if (gdata->vmDead || command->sessionID != currentSessionID) {
+            log_debugee_location("dequeueCommand(): command session removal", NULL, NULL, 0);
+            completeCommand(command);
+            command = NULL;
+        }
+
+        /*
+         * There's room in the queue for more.
+         */
+        currentQueueSize -= size;
+        debugMonitorNotifyAll(commandQueueLock);
+    }
+
+    debugMonitorExit(commandQueueLock);
+
+    return command;
+}
+
+void eventHelper_holdEvents(void)
+{
+    debugMonitorEnter(commandQueueLock);
+    holdEvents = JNI_TRUE;
+    debugMonitorNotifyAll(commandQueueLock);
+    debugMonitorExit(commandQueueLock);
+}
+
+void eventHelper_releaseEvents(void)
+{
+    debugMonitorEnter(commandQueueLock);
+    holdEvents = JNI_FALSE;
+    debugMonitorNotifyAll(commandQueueLock);
+    debugMonitorExit(commandQueueLock);
+}
+
+static void
+writeSingleStepEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    writeCodeLocation(out, evinfo->clazz, evinfo->method, evinfo->location);
+}
+
+static void
+writeBreakpointEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    writeCodeLocation(out, evinfo->clazz, evinfo->method, evinfo->location);
+}
+
+static void
+writeFieldAccessEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    jbyte fieldClassTag;
+
+    fieldClassTag = referenceTypeTag(evinfo->u.field_access.field_clazz);
+
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    writeCodeLocation(out, evinfo->clazz, evinfo->method, evinfo->location);
+    (void)outStream_writeByte(out, fieldClassTag);
+    (void)outStream_writeObjectRef(env, out, evinfo->u.field_access.field_clazz);
+    (void)outStream_writeFieldID(out, evinfo->u.field_access.field);
+    (void)outStream_writeObjectTag(env, out, evinfo->object);
+    (void)outStream_writeObjectRef(env, out, evinfo->object);
+}
+
+static void
+writeFieldModificationEvent(JNIEnv *env, PacketOutputStream *out,
+                            EventInfo *evinfo)
+{
+    jbyte fieldClassTag;
+
+    fieldClassTag = referenceTypeTag(evinfo->u.field_modification.field_clazz);
+
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    writeCodeLocation(out, evinfo->clazz, evinfo->method, evinfo->location);
+    (void)outStream_writeByte(out, fieldClassTag);
+    (void)outStream_writeObjectRef(env, out, evinfo->u.field_modification.field_clazz);
+    (void)outStream_writeFieldID(out, evinfo->u.field_modification.field);
+    (void)outStream_writeObjectTag(env, out, evinfo->object);
+    (void)outStream_writeObjectRef(env, out, evinfo->object);
+    (void)outStream_writeValue(env, out, (jbyte)evinfo->u.field_modification.signature_type,
+                         evinfo->u.field_modification.new_value);
+}
+
+static void
+writeExceptionEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    writeCodeLocation(out, evinfo->clazz, evinfo->method, evinfo->location);
+    (void)outStream_writeObjectTag(env, out, evinfo->object);
+    (void)outStream_writeObjectRef(env, out, evinfo->object);
+    writeCodeLocation(out, evinfo->u.exception.catch_clazz,
+                      evinfo->u.exception.catch_method, evinfo->u.exception.catch_location);
+}
+
+static void
+writeThreadEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+}
+
+static void
+writeMonitorEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    jclass klass;
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    (void)outStream_writeObjectTag(env, out, evinfo->object);
+    (void)outStream_writeObjectRef(env, out, evinfo->object);
+    if (evinfo->ei == EI_MONITOR_WAIT || evinfo->ei == EI_MONITOR_WAITED) {
+        /* clazz of evinfo was set to class of monitor object for monitor wait event class filtering.
+         * So get the method class to write location info.
+         * See cbMonitorWait() and cbMonitorWaited() function in eventHandler.c.
+         */
+        klass=getMethodClass(gdata->jvmti, evinfo->method);
+        writeCodeLocation(out, klass, evinfo->method, evinfo->location);
+        if (evinfo->ei == EI_MONITOR_WAIT) {
+            (void)outStream_writeLong(out, evinfo->u.monitor.timeout);
+        } else  if (evinfo->ei == EI_MONITOR_WAITED) {
+            (void)outStream_writeBoolean(out, evinfo->u.monitor.timed_out);
+        }
+        /* This runs in a command loop and this thread may not return to java.
+         * So we need to delete the local ref created by jvmti GetMethodDeclaringClass.
+         */
+        JNI_FUNC_PTR(env,DeleteLocalRef)(env, klass);
+    } else {
+        writeCodeLocation(out, evinfo->clazz, evinfo->method, evinfo->location);
+    }
+}
+
+static void
+writeClassEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+    jbyte classTag;
+    jint status;
+    char *signature = NULL;
+    jvmtiError error;
+
+    classTag = referenceTypeTag(evinfo->clazz);
+    error = classSignature(evinfo->clazz, &signature, NULL);
+    if (error != JVMTI_ERROR_NONE) {
+        EXIT_ERROR(error,"signature");
+    }
+    status = classStatus(evinfo->clazz);
+
+    (void)outStream_writeObjectRef(env, out, evinfo->thread);
+    (void)outStream_writeByte(out, classTag);
+    (void)outStream_writeObjectRef(env, out, evinfo->clazz);
+    (void)outStream_writeString(out, signature);
+    (void)outStream_writeInt(out, map2jdwpClassStatus(status));
+    jvmtiDeallocate(signature);
+}
+
+static void
+writeVMDeathEvent(JNIEnv *env, PacketOutputStream *out, EventInfo *evinfo)
+{
+}
+
+static void
+handleEventCommandSingle(JNIEnv *env, PacketOutputStream *out,
+                           EventCommandSingle *command)
+{
+    EventInfo *evinfo = &command->info;
+
+    (void)outStream_writeByte(out, eventIndex2jdwp(evinfo->ei));
+    (void)outStream_writeInt(out, command->id);
+
+    switch (evinfo->ei) {
+        case EI_SINGLE_STEP:
+            writeSingleStepEvent(env, out, evinfo);
+            break;
+        case EI_BREAKPOINT:
+            writeBreakpointEvent(env, out, evinfo);
+            break;
+        case EI_FIELD_ACCESS:
+            writeFieldAccessEvent(env, out, evinfo);
+            break;
+        case EI_FIELD_MODIFICATION:
+            writeFieldModificationEvent(env, out, evinfo);
+            break;
+        case EI_EXCEPTION:
+            writeExceptionEvent(env, out, evinfo);
+            break;
+        case EI_THREAD_START:
+        case EI_THREAD_END:
+            writeThreadEvent(env, out, evinfo);
+            break;
+        case EI_CLASS_LOAD:
+        case EI_CLASS_PREPARE:
+            writeClassEvent(env, out, evinfo);
+            break;
+        case EI_MONITOR_CONTENDED_ENTER:
+        case EI_MONITOR_CONTENDED_ENTERED:
+        case EI_MONITOR_WAIT:
+        case EI_MONITOR_WAITED:
+            writeMonitorEvent(env, out, evinfo);
+            break;
+        case EI_VM_DEATH:
+            writeVMDeathEvent(env, out, evinfo);
+            break;
+        default:
+            EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE,"unknown event index");
+            break;
+    }
+    tossEventInfoRefs(env, evinfo);
+}
+
+static void
+handleUnloadCommandSingle(JNIEnv* env, PacketOutputStream *out,
+                           UnloadCommandSingle *command)
+{
+    (void)outStream_writeByte(out, JDWP_EVENT(CLASS_UNLOAD));
+    (void)outStream_writeInt(out, command->id);
+    (void)outStream_writeString(out, command->classSignature);
+    jvmtiDeallocate(command->classSignature);
+    command->classSignature = NULL;
+}
+
+static void
+handleFrameEventCommandSingle(JNIEnv* env, PacketOutputStream *out,
+                              FrameEventCommandSingle *command)
+{
+    if (command->typeKey) {
+        (void)outStream_writeByte(out, JDWP_EVENT(METHOD_EXIT_WITH_RETURN_VALUE));
+    } else {
+        (void)outStream_writeByte(out, eventIndex2jdwp(command->ei));
+    }
+    (void)outStream_writeInt(out, command->id);
+    (void)outStream_writeObjectRef(env, out, command->thread);
+    writeCodeLocation(out, command->clazz, command->method, command->location);
+    if (command->typeKey) {
+        (void)outStream_writeValue(env, out, command->typeKey, command->returnValue);
+        if (isObjectTag(command->typeKey) &&
+            command->returnValue.l != NULL) {
+            tossGlobalRef(env, &(command->returnValue.l));
+        }
+    }
+    tossGlobalRef(env, &(command->thread));
+    tossGlobalRef(env, &(command->clazz));
+}
+
+static void
+suspendWithInvokeEnabled(jbyte policy, jthread thread)
+{
+    invoker_enableInvokeRequests(thread);
+
+    if (policy == JDWP_SUSPEND_POLICY(ALL)) {
+        (void)threadControl_suspendAll();
+    } else {
+        (void)threadControl_suspendThread(thread, JNI_FALSE);
+    }
+}
+
+static void
+handleReportEventCompositeCommand(JNIEnv *env,
+                                  ReportEventCompositeCommand *recc)
+{
+    PacketOutputStream out;
+    jint count = recc->eventCount;
+    jint i;
+
+    if (recc->suspendPolicy != JDWP_SUSPEND_POLICY(NONE)) {
+        /* must determine thread to interrupt before writing */
+        /* since writing destroys it */
+        jthread thread = NULL;
+        for (i = 0; i < count; i++) {
+            CommandSingle *single = &(recc->singleCommand[i]);
+            switch (single->singleKind) {
+                case COMMAND_SINGLE_EVENT:
+                    thread = single->u.eventCommand.info.thread;
+                    break;
+                case COMMAND_SINGLE_FRAME_EVENT:
+                    thread = single->u.frameEventCommand.thread;
+                    break;
+            }
+            if (thread != NULL) {
+                break;
+            }
+        }
+
+        if (thread == NULL) {
+            (void)threadControl_suspendAll();
+        } else {
+            suspendWithInvokeEnabled(recc->suspendPolicy, thread);
+        }
+    }
+
+    outStream_initCommand(&out, uniqueID(), 0x0,
+                          JDWP_COMMAND_SET(Event),
+                          JDWP_COMMAND(Event, Composite));
+    (void)outStream_writeByte(&out, recc->suspendPolicy);
+    (void)outStream_writeInt(&out, count);
+
+    for (i = 0; i < count; i++) {
+        CommandSingle *single = &(recc->singleCommand[i]);
+        switch (single->singleKind) {
+            case COMMAND_SINGLE_EVENT:
+                handleEventCommandSingle(env, &out,
+                                         &single->u.eventCommand);
+                break;
+            case COMMAND_SINGLE_UNLOAD:
+                handleUnloadCommandSingle(env, &out,
+                                          &single->u.unloadCommand);
+                break;
+            case COMMAND_SINGLE_FRAME_EVENT:
+                handleFrameEventCommandSingle(env, &out,
+                                              &single->u.frameEventCommand);
+                break;
+        }
+    }
+
+    outStream_sendCommand(&out);
+    outStream_destroy(&out);
+}
+
+static void
+handleReportInvokeDoneCommand(JNIEnv* env, ReportInvokeDoneCommand *command)
+{
+    invoker_completeInvokeRequest(command->thread);
+    tossGlobalRef(env, &(command->thread));
+}
+
+static void
+handleReportVMInitCommand(JNIEnv* env, ReportVMInitCommand *command)
+{
+    PacketOutputStream out;
+
+    if (command->suspendPolicy == JDWP_SUSPEND_POLICY(ALL)) {
+        (void)threadControl_suspendAll();
+    } else if (command->suspendPolicy == JDWP_SUSPEND_POLICY(EVENT_THREAD)) {
+        (void)threadControl_suspendThread(command->thread, JNI_FALSE);
+    }
+
+    outStream_initCommand(&out, uniqueID(), 0x0,
+                          JDWP_COMMAND_SET(Event),
+                          JDWP_COMMAND(Event, Composite));
+    (void)outStream_writeByte(&out, command->suspendPolicy);
+    (void)outStream_writeInt(&out, 1);   /* Always one component */
+    (void)outStream_writeByte(&out, JDWP_EVENT(VM_INIT));
+    (void)outStream_writeInt(&out, 0);    /* Not in response to an event req. */
+
+    (void)outStream_writeObjectRef(env, &out, command->thread);
+
+    outStream_sendCommand(&out);
+    outStream_destroy(&out);
+    /* Why aren't we tossing this: tossGlobalRef(env, &(command->thread)); */
+}
+
+static void
+handleSuspendThreadCommand(JNIEnv* env, SuspendThreadCommand *command)
+{
+    /*
+     * For the moment, there's  nothing that can be done with the
+     * return code, so we don't check it here.
+     */
+    (void)threadControl_suspendThread(command->thread, JNI_TRUE);
+    tossGlobalRef(env, &(command->thread));
+}
+
+static void
+handleCommand(JNIEnv *env, HelperCommand *command)
+{
+    switch (command->commandKind) {
+        case COMMAND_REPORT_EVENT_COMPOSITE:
+            handleReportEventCompositeCommand(env,
+                                        &command->u.reportEventComposite);
+            break;
+        case COMMAND_REPORT_INVOKE_DONE:
+            handleReportInvokeDoneCommand(env, &command->u.reportInvokeDone);
+            break;
+        case COMMAND_REPORT_VM_INIT:
+            handleReportVMInitCommand(env, &command->u.reportVMInit);
+            break;
+        case COMMAND_SUSPEND_THREAD:
+            handleSuspendThreadCommand(env, &command->u.suspendThread);
+            break;
+        default:
+            EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE,"Event Helper Command");
+            break;
+    }
+}
+
+/*
+ * There was an assumption that only one event with a suspend-all
+ * policy could be processed by commandLoop() at one time. It was
+ * assumed that native thread suspension from the first suspend-all
+ * event would prevent the second suspend-all event from making it
+ * into the command queue. For the Classic VM, this was a reasonable
+ * assumption. However, in HotSpot all thread suspension requires a
+ * VM operation and VM operations take time.
+ *
+ * The solution is to add a mechanism to prevent commandLoop() from
+ * processing more than one event with a suspend-all policy. This is
+ * accomplished by forcing commandLoop() to wait for either
+ * ThreadReferenceImpl.c: resume() or VirtualMachineImpl.c: resume()
+ * when an event with a suspend-all policy has been completed.
+ */
+static jboolean blockCommandLoop = JNI_FALSE;
+
+/*
+ * We wait for either ThreadReferenceImpl.c: resume() or
+ * VirtualMachineImpl.c: resume() to be called.
+ */
+static void
+doBlockCommandLoop(void) {
+    debugMonitorEnter(blockCommandLoopLock);
+    while (blockCommandLoop == JNI_TRUE) {
+        debugMonitorWait(blockCommandLoopLock);
+    }
+    debugMonitorExit(blockCommandLoopLock);
+}
+
+/*
+ * If the command that we are about to execute has a suspend-all
+ * policy, then prepare for either ThreadReferenceImpl.c: resume()
+ * or VirtualMachineImpl.c: resume() to be called.
+ */
+static jboolean
+needBlockCommandLoop(HelperCommand *cmd) {
+    if (cmd->commandKind == COMMAND_REPORT_EVENT_COMPOSITE
+    && cmd->u.reportEventComposite.suspendPolicy == JDWP_SUSPEND_POLICY(ALL)) {
+        debugMonitorEnter(blockCommandLoopLock);
+        blockCommandLoop = JNI_TRUE;
+        debugMonitorExit(blockCommandLoopLock);
+
+        return JNI_TRUE;
+    }
+
+    return JNI_FALSE;
+}
+
+/*
+ * Used by either ThreadReferenceImpl.c: resume() or
+ * VirtualMachineImpl.c: resume() to resume commandLoop().
+ */
+void
+unblockCommandLoop(void) {
+    debugMonitorEnter(blockCommandLoopLock);
+    blockCommandLoop = JNI_FALSE;
+    debugMonitorNotifyAll(blockCommandLoopLock);
+    debugMonitorExit(blockCommandLoopLock);
+}
+
+/*
+ * The event helper thread. Dequeues commands and processes them.
+ */
+static void JNICALL
+commandLoop(jvmtiEnv* jvmti_env, JNIEnv* jni_env, void* arg)
+{
+    LOG_MISC(("Begin command loop thread"));
+
+    while (JNI_TRUE) {
+        HelperCommand *command = dequeueCommand();
+        if (command != NULL) {
+            /*
+             * Setup for a potential doBlockCommand() call before calling
+             * handleCommand() to prevent any races.
+             */
+            jboolean doBlock = needBlockCommandLoop(command);
+            log_debugee_location("commandLoop(): command being handled", NULL, NULL, 0);
+            handleCommand(jni_env, command);
+            completeCommand(command);
+            /* if we just finished a suspend-all cmd, then we block here */
+            if (doBlock) {
+                doBlockCommandLoop();
+            }
+        }
+    }
+    /* This loop never ends, even as connections come and go with server=y */
+}
+
+void
+eventHelper_initialize(jbyte sessionID)
+{
+    jvmtiStartFunction func;
+
+    currentSessionID = sessionID;
+    holdEvents = JNI_FALSE;
+    commandQueue.head = NULL;
+    commandQueue.tail = NULL;
+
+    commandQueueLock = debugMonitorCreate("JDWP Event Helper Queue Monitor");
+    commandCompleteLock = debugMonitorCreate("JDWP Event Helper Completion Monitor");
+    blockCommandLoopLock = debugMonitorCreate("JDWP Event Block CommandLoop Monitor");
+
+    /* Start the event handler thread */
+    func = &commandLoop;
+    (void)spawnNewThread(func, NULL, "JDWP Event Helper Thread");
+}
+
+void
+eventHelper_reset(jbyte newSessionID)
+{
+    debugMonitorEnter(commandQueueLock);
+    currentSessionID = newSessionID;
+    holdEvents = JNI_FALSE;
+    debugMonitorNotifyAll(commandQueueLock);
+    debugMonitorExit(commandQueueLock);
+}
+
+/*
+ * Provide a means for threadControl to ensure that crucial locks are not
+ * held by suspended threads.
+ */
+void
+eventHelper_lock(void)
+{
+    debugMonitorEnter(commandQueueLock);
+    debugMonitorEnter(commandCompleteLock);
+}
+
+void
+eventHelper_unlock(void)
+{
+    debugMonitorExit(commandCompleteLock);
+    debugMonitorExit(commandQueueLock);
+}
+
+/* Change all references to global in the EventInfo struct */
+static void
+saveEventInfoRefs(JNIEnv *env, EventInfo *evinfo)
+{
+    jthread *pthread;
+    jclass *pclazz;
+    jobject *pobject;
+    jthread thread;
+    jclass clazz;
+    jobject object;
+    char sig;
+
+    JNI_FUNC_PTR(env,ExceptionClear)(env);
+
+    if ( evinfo->thread != NULL ) {
+        pthread = &(evinfo->thread);
+        thread = *pthread;
+        *pthread = NULL;
+        saveGlobalRef(env, thread, pthread);
+    }
+    if ( evinfo->clazz != NULL ) {
+        pclazz = &(evinfo->clazz);
+        clazz = *pclazz;
+        *pclazz = NULL;
+        saveGlobalRef(env, clazz, pclazz);
+    }
+    if ( evinfo->object != NULL ) {
+        pobject = &(evinfo->object);
+        object = *pobject;
+        *pobject = NULL;
+        saveGlobalRef(env, object, pobject);
+    }
+
+    switch (evinfo->ei) {
+        case EI_FIELD_MODIFICATION:
+            if ( evinfo->u.field_modification.field_clazz != NULL ) {
+                pclazz = &(evinfo->u.field_modification.field_clazz);
+                clazz = *pclazz;
+                *pclazz = NULL;
+                saveGlobalRef(env, clazz, pclazz);
+            }
+            sig = evinfo->u.field_modification.signature_type;
+            if ((sig == JDWP_TAG(ARRAY)) || (sig == JDWP_TAG(OBJECT))) {
+                if ( evinfo->u.field_modification.new_value.l != NULL ) {
+                    pobject = &(evinfo->u.field_modification.new_value.l);
+                    object = *pobject;
+                    *pobject = NULL;
+                    saveGlobalRef(env, object, pobject);
+                }
+            }
+            break;
+        case EI_FIELD_ACCESS:
+            if ( evinfo->u.field_access.field_clazz != NULL ) {
+                pclazz = &(evinfo->u.field_access.field_clazz);
+                clazz = *pclazz;
+                *pclazz = NULL;
+                saveGlobalRef(env, clazz, pclazz);
+            }
+            break;
+        case EI_EXCEPTION:
+            if ( evinfo->u.exception.catch_clazz != NULL ) {
+                pclazz = &(evinfo->u.exception.catch_clazz);
+                clazz = *pclazz;
+                *pclazz = NULL;
+                saveGlobalRef(env, clazz, pclazz);
+            }
+            break;
+        default:
+            break;
+    }
+
+    if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) {
+        EXIT_ERROR(AGENT_ERROR_INVALID_EVENT_TYPE,"ExceptionOccurred");
+    }
+}
+
+static void
+tossEventInfoRefs(JNIEnv *env, EventInfo *evinfo)
+{
+    char sig;
+    if ( evinfo->thread != NULL ) {
+        tossGlobalRef(env, &(evinfo->thread));
+    }
+    if ( evinfo->clazz != NULL ) {
+        tossGlobalRef(env, &(evinfo->clazz));
+    }
+    if ( evinfo->object != NULL ) {
+        tossGlobalRef(env, &(evinfo->object));
+    }
+    switch (evinfo->ei) {
+        case EI_FIELD_MODIFICATION:
+            if ( evinfo->u.field_modification.field_clazz != NULL ) {
+                tossGlobalRef(env, &(evinfo->u.field_modification.field_clazz));
+            }
+            sig = evinfo->u.field_modification.signature_type;
+            if ((sig == JDWP_TAG(ARRAY)) || (sig == JDWP_TAG(OBJECT))) {
+                if ( evinfo->u.field_modification.new_value.l != NULL ) {
+                    tossGlobalRef(env, &(evinfo->u.field_modification.new_value.l));
+                }
+            }
+            break;
+        case EI_FIELD_ACCESS:
+            if ( evinfo->u.field_access.field_clazz != NULL ) {
+                tossGlobalRef(env, &(evinfo->u.field_access.field_clazz));
+            }
+            break;
+        case EI_EXCEPTION:
+            if ( evinfo->u.exception.catch_clazz != NULL ) {
+                tossGlobalRef(env, &(evinfo->u.exception.catch_clazz));
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+struct bag *
+eventHelper_createEventBag(void)
+{
+    return bagCreateBag(sizeof(CommandSingle), 5 /* events */ );
+}
+
+/* Return the combined suspend policy for the event set
+ */
+static jboolean
+enumForCombinedSuspendPolicy(void *cv, void *arg)
+{
+    CommandSingle *command = cv;
+    jbyte thisPolicy;
+    jbyte *policy = arg;
+
+    switch(command->singleKind) {
+        case COMMAND_SINGLE_EVENT:
+            thisPolicy = command->u.eventCommand.suspendPolicy;
+            break;
+        case COMMAND_SINGLE_FRAME_EVENT:
+            thisPolicy = command->u.frameEventCommand.suspendPolicy;
+            break;
+        default:
+            thisPolicy = JDWP_SUSPEND_POLICY(NONE);
+    }
+    /* Expand running policy value if this policy demands it */
+    if (*policy == JDWP_SUSPEND_POLICY(NONE)) {
+        *policy = thisPolicy;
+    } else if (*policy == JDWP_SUSPEND_POLICY(EVENT_THREAD)) {
+        *policy = (thisPolicy == JDWP_SUSPEND_POLICY(ALL))?
+                        thisPolicy : *policy;
+    }
+
+    /* Short circuit if we reached maximal suspend policy */
+    if (*policy == JDWP_SUSPEND_POLICY(ALL)) {
+        return JNI_FALSE;
+    } else {
+        return JNI_TRUE;
+    }
+}
+
+/* Determine whether we are reporting VM death
+ */
+static jboolean
+enumForVMDeath(void *cv, void *arg)
+{
+    CommandSingle *command = cv;
+    jboolean *reportingVMDeath = arg;
+
+    if (command->singleKind == COMMAND_SINGLE_EVENT) {
+        if (command->u.eventCommand.info.ei == EI_VM_DEATH) {
+            *reportingVMDeath = JNI_TRUE;
+            return JNI_FALSE;
+        }
+    }
+    return JNI_TRUE;
+}
+
+struct singleTracker {
+    ReportEventCompositeCommand *recc;
+    int index;
+};
+
+static jboolean
+enumForCopyingSingles(void *command, void *tv)
+{
+    struct singleTracker *tracker = (struct singleTracker *)tv;
+    (void)memcpy(&tracker->recc->singleCommand[tracker->index++],
+           command,
+           sizeof(CommandSingle));
+    return JNI_TRUE;
+}
+
+jbyte
+eventHelper_reportEvents(jbyte sessionID, struct bag *eventBag)
+{
+    int size = bagSize(eventBag);
+    jbyte suspendPolicy = JDWP_SUSPEND_POLICY(NONE);
+    jboolean reportingVMDeath = JNI_FALSE;
+    jboolean wait;
+    int command_size;
+
+    HelperCommand *command;
+    ReportEventCompositeCommand *recc;
+    struct singleTracker tracker;
+
+    if (size == 0) {
+        return suspendPolicy;
+    }
+    (void)bagEnumerateOver(eventBag, enumForCombinedSuspendPolicy, &suspendPolicy);
+    (void)bagEnumerateOver(eventBag, enumForVMDeath, &reportingVMDeath);
+
+    /*LINTED*/
+    command_size = (int)(sizeof(HelperCommand) +
+                         sizeof(CommandSingle)*(size-1));
+    command = jvmtiAllocate(command_size);
+    (void)memset(command, 0, command_size);
+    command->commandKind = COMMAND_REPORT_EVENT_COMPOSITE;
+    command->sessionID = sessionID;
+    recc = &command->u.reportEventComposite;
+    recc->suspendPolicy = suspendPolicy;
+    recc->eventCount = size;
+    tracker.recc = recc;
+    tracker.index = 0;
+    (void)bagEnumerateOver(eventBag, enumForCopyingSingles, &tracker);
+
+    /*
+     * We must wait if this thread (the event thread) is to be
+     * suspended or if the VM is about to die. (Waiting in the latter
+     * case ensures that we get the event out before the process dies.)
+     */
+    wait = (jboolean)((suspendPolicy != JDWP_SUSPEND_POLICY(NONE)) ||
+                      reportingVMDeath);
+    enqueueCommand(command, wait, reportingVMDeath);
+    return suspendPolicy;
+}
+
+void
+eventHelper_recordEvent(EventInfo *evinfo, jint id, jbyte suspendPolicy,
+                         struct bag *eventBag)
+{
+    JNIEnv *env = getEnv();
+    CommandSingle *command = bagAdd(eventBag);
+    if (command == NULL) {
+        EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"badAdd(eventBag)");
+    }
+
+    command->singleKind = COMMAND_SINGLE_EVENT;
+    command->u.eventCommand.suspendPolicy = suspendPolicy;
+    command->u.eventCommand.id = id;
+
+    /*
+     * Copy the event into the command so that it can be used
+     * asynchronously by the event helper thread.
+     */
+    (void)memcpy(&command->u.eventCommand.info, evinfo, sizeof(*evinfo));
+    saveEventInfoRefs(env, &command->u.eventCommand.info);
+}
+
+void
+eventHelper_recordClassUnload(jint id, char *signature, struct bag *eventBag)
+{
+    CommandSingle *command = bagAdd(eventBag);
+    if (command == NULL) {
+        EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"bagAdd(eventBag)");
+    }
+    command->singleKind = COMMAND_SINGLE_UNLOAD;
+    command->u.unloadCommand.id = id;
+    command->u.unloadCommand.classSignature = signature;
+}
+
+void
+eventHelper_recordFrameEvent(jint id, jbyte suspendPolicy, EventIndex ei,
+                             jthread thread, jclass clazz,
+                             jmethodID method, jlocation location,
+                             int needReturnValue,
+                             jvalue returnValue,
+                             struct bag *eventBag)
+{
+    JNIEnv *env = getEnv();
+    FrameEventCommandSingle *frameCommand;
+    CommandSingle *command = bagAdd(eventBag);
+    jvmtiError err = JVMTI_ERROR_NONE;
+    if (command == NULL) {
+        EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"bagAdd(eventBag)");
+    }
+
+    command->singleKind = COMMAND_SINGLE_FRAME_EVENT;
+    frameCommand = &command->u.frameEventCommand;
+    frameCommand->suspendPolicy = suspendPolicy;
+    frameCommand->id = id;
+    frameCommand->ei = ei;
+    saveGlobalRef(env, thread, &(frameCommand->thread));
+    saveGlobalRef(env, clazz, &(frameCommand->clazz));
+    frameCommand->method = method;
+    frameCommand->location = location;
+    if (needReturnValue) {
+        err = methodReturnType(method, &frameCommand->typeKey);
+        JDI_ASSERT(err == JVMTI_ERROR_NONE);
+
+        /*
+         * V or B C D F I J S Z L <classname> ;    [ ComponentType
+         */
+        if (isObjectTag(frameCommand->typeKey) &&
+            returnValue.l != NULL) {
+            saveGlobalRef(env, returnValue.l, &(frameCommand->returnValue.l));
+        } else {
+            frameCommand->returnValue = returnValue;
+        }
+    } else {
+      /* This is not a JDWP METHOD_EXIT_WITH_RETURN_VALUE request,
+       * so signal this by setting typeKey = 0 which is not
+       * a legal typekey.
+       */
+       frameCommand->typeKey = 0;
+    }
+}
+
+void
+eventHelper_reportInvokeDone(jbyte sessionID, jthread thread)
+{
+    JNIEnv *env = getEnv();
+    HelperCommand *command = jvmtiAllocate(sizeof(*command));
+    if (command == NULL) {
+        EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"HelperCommand");
+    }
+    (void)memset(command, 0, sizeof(*command));
+    command->commandKind = COMMAND_REPORT_INVOKE_DONE;
+    command->sessionID = sessionID;
+    saveGlobalRef(env, thread, &(command->u.reportInvokeDone.thread));
+    enqueueCommand(command, JNI_TRUE, JNI_FALSE);
+}
+
+/*
+ * This, currently, cannot go through the normal event handling code
+ * because the JVMTI event does not contain a thread.
+ */
+void
+eventHelper_reportVMInit(JNIEnv *env, jbyte sessionID, jthread thread, jbyte suspendPolicy)
+{
+    HelperCommand *command = jvmtiAllocate(sizeof(*command));
+    if (command == NULL) {
+        EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"HelperCommmand");
+    }
+    (void)memset(command, 0, sizeof(*command));
+    command->commandKind = COMMAND_REPORT_VM_INIT;
+    command->sessionID = sessionID;
+    saveGlobalRef(env, thread, &(command->u.reportVMInit.thread));
+    command->u.reportVMInit.suspendPolicy = suspendPolicy;
+    enqueueCommand(command, JNI_TRUE, JNI_FALSE);
+}
+
+void
+eventHelper_suspendThread(jbyte sessionID, jthread thread)
+{
+    JNIEnv *env = getEnv();
+    HelperCommand *command = jvmtiAllocate(sizeof(*command));
+    if (command == NULL) {
+        EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"HelperCommmand");
+    }
+    (void)memset(command, 0, sizeof(*command));
+    command->commandKind = COMMAND_SUSPEND_THREAD;
+    command->sessionID = sessionID;
+    saveGlobalRef(env, thread, &(command->u.suspendThread.thread));
+    enqueueCommand(command, JNI_TRUE, JNI_FALSE);
+}