/* * Copyright (c) 1998, 2019, 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" #define COMMAND_LOOP_THREAD_NAME "JDWP Event Helper Thread" /* * 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 jrawMonitorID vmDeathLock; static volatile jboolean commandLoopEnteredVmDeathLock = JNI_FALSE; 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); debugMonitorEnter(vmDeathLock); commandLoopEnteredVmDeathLock = JNI_TRUE; if (!gdata->vmDead) { log_debugee_location("commandLoop(): command being handled", NULL, NULL, 0); handleCommand(jni_env, command); } completeCommand(command); debugMonitorExit(vmDeathLock); commandLoopEnteredVmDeathLock = JNI_FALSE; /* 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"); vmDeathLock = debugMonitorCreate("JDWP VM_DEATH CommandLoop Monitor"); /* Start the event handler thread */ func = &commandLoop; (void)spawnNewThread(func, NULL, COMMAND_LOOP_THREAD_NAME); } void eventHelper_reset(jbyte newSessionID) { debugMonitorEnter(commandQueueLock); currentSessionID = newSessionID; holdEvents = JNI_FALSE; debugMonitorNotifyAll(commandQueueLock); debugMonitorExit(commandQueueLock); unblockCommandLoop(); } /* * 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); } void commandLoop_exitVmDeathLockOnError() { const char* MSG_BASE = "exitVmDeathLockOnError: error in JVMTI %s: %d\n"; jthread cur_thread = NULL; jvmtiThreadInfo thread_info; jvmtiError err = JVMTI_ERROR_NONE; err = JVMTI_FUNC_PTR(gdata->jvmti, GetCurrentThread) (gdata->jvmti, &cur_thread); if (err != JVMTI_ERROR_NONE) { LOG_ERROR((MSG_BASE, "GetCurrentThread", err)); return; } err = JVMTI_FUNC_PTR(gdata->jvmti, GetThreadInfo) (gdata->jvmti, cur_thread, &thread_info); if (err != JVMTI_ERROR_NONE) { LOG_ERROR((MSG_BASE, "GetThreadInfo", err)); return; } if (strcmp(thread_info.name, COMMAND_LOOP_THREAD_NAME) != 0) { return; } if (commandLoopEnteredVmDeathLock == JNI_TRUE) { debugMonitorExit(vmDeathLock); commandLoopEnteredVmDeathLock = JNI_FALSE; } } void commandLoop_sync(void) { debugMonitorEnter(vmDeathLock); debugMonitorExit(vmDeathLock); } /* 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)) || (sig == JDWP_TAG(INLINE_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)) || (sig == JDWP_TAG(INLINE_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); }