blob: a17a091aa34fb5709434c0e1f97daf939f55e3b0 [file] [log] [blame]
/*
* Copyright (c) 1998, 2006, 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
* previously attached debugger.
*/
if (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);
}