diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/jdk.jdwp.agent/share/native/libjdwp/eventHelper.c --- /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 ; [ 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); +}