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