| /* |
| * Copyright (c) 2001, 2013, 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. |
| */ |
| /* |
| * eventFilter |
| * |
| * This module handles event filteration and the enabling/disabling |
| * of the corresponding events. Used for filters on JDI EventRequests |
| * and also internal requests. Our data is in a private hidden section |
| * of the HandlerNode's data. See comment for enclosing |
| * module eventHandler. |
| */ |
| |
| #include "util.h" |
| #include "eventFilter.h" |
| #include "eventFilterRestricted.h" |
| #include "eventHandlerRestricted.h" |
| #include "stepControl.h" |
| #include "threadControl.h" |
| #include "SDE.h" |
| #include "jvmti.h" |
| |
| typedef struct ClassFilter { |
| jclass clazz; |
| } ClassFilter; |
| |
| typedef struct LocationFilter { |
| jclass clazz; |
| jmethodID method; |
| jlocation location; |
| } LocationFilter; |
| |
| typedef struct ThreadFilter { |
| jthread thread; |
| } ThreadFilter; |
| |
| typedef struct CountFilter { |
| jint count; |
| } CountFilter; |
| |
| typedef struct ConditionalFilter { |
| jint exprID; |
| } ConditionalFilter; |
| |
| typedef struct FieldFilter { |
| jclass clazz; |
| jfieldID field; |
| } FieldFilter; |
| |
| typedef struct ExceptionFilter { |
| jclass exception; |
| jboolean caught; |
| jboolean uncaught; |
| } ExceptionFilter; |
| |
| typedef struct InstanceFilter { |
| jobject instance; |
| } InstanceFilter; |
| |
| typedef struct StepFilter { |
| jint size; |
| jint depth; |
| jthread thread; |
| } StepFilter; |
| |
| typedef struct MatchFilter { |
| char *classPattern; |
| } MatchFilter; |
| |
| typedef struct SourceNameFilter { |
| char *sourceNamePattern; |
| } SourceNameFilter; |
| |
| typedef struct Filter_ { |
| jbyte modifier; |
| union { |
| struct ClassFilter ClassOnly; |
| struct LocationFilter LocationOnly; |
| struct ThreadFilter ThreadOnly; |
| struct CountFilter Count; |
| struct ConditionalFilter Conditional; |
| struct FieldFilter FieldOnly; |
| struct ExceptionFilter ExceptionOnly; |
| struct InstanceFilter InstanceOnly; |
| struct StepFilter Step; |
| struct MatchFilter ClassMatch; |
| struct MatchFilter ClassExclude; |
| struct SourceNameFilter SourceNameOnly; |
| } u; |
| } Filter; |
| |
| /* The filters array is allocated to the specified filterCount. |
| * Theoretically, some compiler could do range checking on this |
| * array - so, we define it to have a ludicrously large size so |
| * that this range checking won't get upset. |
| * |
| * The actual allocated number of bytes is computed using the |
| * offset of "filters" and so is not effected by this number. |
| */ |
| #define MAX_FILTERS 10000 |
| |
| typedef struct EventFilters_ { |
| jint filterCount; |
| Filter filters[MAX_FILTERS]; |
| } EventFilters; |
| |
| typedef struct EventFilterPrivate_HandlerNode_ { |
| EventHandlerRestricted_HandlerNode not_for_us; |
| EventFilters ef; |
| } EventFilterPrivate_HandlerNode; |
| |
| /** |
| * The following macros extract filter info (EventFilters) from private |
| * data at the end of a HandlerNode |
| */ |
| #define EVENT_FILTERS(node) (&(((EventFilterPrivate_HandlerNode*)(void*)node)->ef)) |
| #define FILTER_COUNT(node) (EVENT_FILTERS(node)->filterCount) |
| #define FILTERS_ARRAY(node) (EVENT_FILTERS(node)->filters) |
| #define FILTER(node,index) ((FILTERS_ARRAY(node))[index]) |
| #define NODE_EI(node) (node->ei) |
| |
| /***** filter set-up / destruction *****/ |
| |
| /** |
| * Allocate a HandlerNode. |
| * We do it because eventHandler doesn't know how big to make it. |
| */ |
| HandlerNode * |
| eventFilterRestricted_alloc(jint filterCount) |
| { |
| /*LINTED*/ |
| size_t size = offsetof(EventFilterPrivate_HandlerNode, ef) + |
| offsetof(EventFilters, filters) + |
| (filterCount * (int)sizeof(Filter)); |
| HandlerNode *node = jvmtiAllocate((jint)size); |
| |
| if (node != NULL) { |
| int i; |
| Filter *filter; |
| |
| (void)memset(node, 0, size); |
| |
| FILTER_COUNT(node) = filterCount; |
| |
| /* Initialize all modifiers |
| */ |
| for (i = 0, filter = FILTERS_ARRAY(node); |
| i < filterCount; |
| i++, filter++) { |
| filter->modifier = JDWP_REQUEST_NONE; |
| } |
| } |
| |
| return node; |
| } |
| |
| /** |
| * Free up global refs held by the filter. |
| * free things up at the JNI level if needed. |
| */ |
| static jvmtiError |
| clearFilters(HandlerNode *node) |
| { |
| JNIEnv *env = getEnv(); |
| jint i; |
| jvmtiError error = JVMTI_ERROR_NONE; |
| Filter *filter = FILTERS_ARRAY(node); |
| |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(ThreadOnly): |
| if ( filter->u.ThreadOnly.thread != NULL ) { |
| tossGlobalRef(env, &(filter->u.ThreadOnly.thread)); |
| } |
| break; |
| case JDWP_REQUEST_MODIFIER(LocationOnly): |
| tossGlobalRef(env, &(filter->u.LocationOnly.clazz)); |
| break; |
| case JDWP_REQUEST_MODIFIER(FieldOnly): |
| tossGlobalRef(env, &(filter->u.FieldOnly.clazz)); |
| break; |
| case JDWP_REQUEST_MODIFIER(ExceptionOnly): |
| if ( filter->u.ExceptionOnly.exception != NULL ) { |
| tossGlobalRef(env, &(filter->u.ExceptionOnly.exception)); |
| } |
| break; |
| case JDWP_REQUEST_MODIFIER(InstanceOnly): |
| if ( filter->u.InstanceOnly.instance != NULL ) { |
| tossGlobalRef(env, &(filter->u.InstanceOnly.instance)); |
| } |
| break; |
| case JDWP_REQUEST_MODIFIER(ClassOnly): |
| tossGlobalRef(env, &(filter->u.ClassOnly.clazz)); |
| break; |
| case JDWP_REQUEST_MODIFIER(ClassMatch): |
| jvmtiDeallocate(filter->u.ClassMatch.classPattern); |
| break; |
| case JDWP_REQUEST_MODIFIER(ClassExclude): |
| jvmtiDeallocate(filter->u.ClassExclude.classPattern); |
| break; |
| case JDWP_REQUEST_MODIFIER(Step): { |
| jthread thread = filter->u.Step.thread; |
| error = stepControl_endStep(thread); |
| if (error == JVMTI_ERROR_NONE) { |
| tossGlobalRef(env, &(filter->u.Step.thread)); |
| } |
| break; |
| } |
| } |
| } |
| if (error == JVMTI_ERROR_NONE) { |
| FILTER_COUNT(node) = 0; /* blast so we don't clear again */ |
| } |
| |
| return error; |
| } |
| |
| |
| /***** filtering *****/ |
| |
| /* |
| * Match a string against a wildcard |
| * string pattern. |
| */ |
| static jboolean |
| patternStringMatch(char *classname, const char *pattern) |
| { |
| int pattLen; |
| int compLen; |
| char *start; |
| int offset; |
| |
| if ( pattern==NULL || classname==NULL ) { |
| return JNI_FALSE; |
| } |
| pattLen = (int)strlen(pattern); |
| |
| if ((pattern[0] != '*') && (pattern[pattLen-1] != '*')) { |
| /* An exact match is required when there is no *: bug 4331522 */ |
| return strcmp(pattern, classname) == 0; |
| } else { |
| compLen = pattLen - 1; |
| offset = (int)strlen(classname) - compLen; |
| if (offset < 0) { |
| return JNI_FALSE; |
| } else { |
| if (pattern[0] == '*') { |
| pattern++; |
| start = classname + offset; |
| } else { |
| start = classname; |
| } |
| return strncmp(pattern, start, compLen) == 0; |
| } |
| } |
| } |
| |
| static jboolean isVersionGte12x() { |
| jint version; |
| jvmtiError err = |
| JVMTI_FUNC_PTR(gdata->jvmti,GetVersionNumber)(gdata->jvmti, &version); |
| |
| if (err == JVMTI_ERROR_NONE) { |
| jint major, minor; |
| |
| major = (version & JVMTI_VERSION_MASK_MAJOR) |
| >> JVMTI_VERSION_SHIFT_MAJOR; |
| minor = (version & JVMTI_VERSION_MASK_MINOR) |
| >> JVMTI_VERSION_SHIFT_MINOR; |
| return (major > 1 || (major == 1 && minor >= 2)) ? JNI_TRUE : JNI_FALSE; |
| } else { |
| return JNI_FALSE; |
| } |
| } |
| |
| /* Return the object instance in which the event occurred */ |
| /* Return NULL if static or if an error occurs */ |
| static jobject |
| eventInstance(EventInfo *evinfo) |
| { |
| jobject object = NULL; |
| jthread thread ; |
| jmethodID method ; |
| jint modifiers = 0; |
| jvmtiError error; |
| |
| static jboolean got_version = JNI_FALSE; |
| static jboolean is_version_gte_12x = JNI_FALSE; |
| |
| if (!got_version) { |
| is_version_gte_12x = isVersionGte12x(); |
| got_version = JNI_TRUE; |
| } |
| |
| switch (evinfo->ei) { |
| case EI_SINGLE_STEP: |
| case EI_BREAKPOINT: |
| case EI_FRAME_POP: |
| case EI_METHOD_ENTRY: |
| case EI_METHOD_EXIT: |
| case EI_EXCEPTION: |
| case EI_EXCEPTION_CATCH: |
| case EI_MONITOR_CONTENDED_ENTER: |
| case EI_MONITOR_CONTENDED_ENTERED: |
| case EI_MONITOR_WAIT: |
| case EI_MONITOR_WAITED: |
| thread = evinfo->thread; |
| method = evinfo->method; |
| break; |
| case EI_FIELD_ACCESS: |
| case EI_FIELD_MODIFICATION: |
| object = evinfo->object; |
| return object; |
| default: |
| return object; /* NULL */ |
| } |
| |
| error = methodModifiers(method, &modifiers); |
| |
| /* fail if error or static (0x8) */ |
| if (error == JVMTI_ERROR_NONE && thread!=NULL && (modifiers & 0x8) == 0) { |
| FrameNumber fnum = 0; |
| if (is_version_gte_12x) { |
| /* Use new 1.2.x function, GetLocalInstance */ |
| error = JVMTI_FUNC_PTR(gdata->jvmti,GetLocalInstance) |
| (gdata->jvmti, thread, fnum, &object); |
| } else { |
| /* get slot zero object "this" */ |
| error = JVMTI_FUNC_PTR(gdata->jvmti,GetLocalObject) |
| (gdata->jvmti, thread, fnum, 0, &object); |
| } |
| if (error != JVMTI_ERROR_NONE) { |
| object = NULL; |
| } |
| } |
| |
| return object; |
| } |
| |
| /* |
| * Determine if this event is interesting to this handler. |
| * Do so by checking each of the handler's filters. |
| * Return false if any of the filters fail, |
| * true if the handler wants this event. |
| * Anyone modifying this function should check |
| * eventFilterRestricted_passesUnloadFilter and |
| * eventFilter_predictFiltering as well. |
| * |
| * If shouldDelete is returned true, a count filter has expired |
| * and the corresponding node should be deleted. |
| */ |
| jboolean |
| eventFilterRestricted_passesFilter(JNIEnv *env, |
| char *classname, |
| EventInfo *evinfo, |
| HandlerNode *node, |
| jboolean *shouldDelete) |
| { |
| jthread thread; |
| jclass clazz; |
| jmethodID method; |
| Filter *filter = FILTERS_ARRAY(node); |
| int i; |
| |
| *shouldDelete = JNI_FALSE; |
| thread = evinfo->thread; |
| clazz = evinfo->clazz; |
| method = evinfo->method; |
| |
| /* |
| * Suppress most events if they happen in debug threads |
| */ |
| if ((evinfo->ei != EI_CLASS_PREPARE) && |
| (evinfo->ei != EI_GC_FINISH) && |
| (evinfo->ei != EI_CLASS_LOAD) && |
| threadControl_isDebugThread(thread)) { |
| return JNI_FALSE; |
| } |
| |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(ThreadOnly): |
| if (!isSameObject(env, thread, filter->u.ThreadOnly.thread)) { |
| return JNI_FALSE; |
| } |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(ClassOnly): |
| /* Class filters catch events in the specified |
| * class and any subclass/subinterface. |
| */ |
| if (!JNI_FUNC_PTR(env,IsAssignableFrom)(env, clazz, |
| filter->u.ClassOnly.clazz)) { |
| return JNI_FALSE; |
| } |
| break; |
| |
| /* This is kinda cheating assumming the event |
| * fields will be in the same locations, but it is |
| * true now. |
| */ |
| case JDWP_REQUEST_MODIFIER(LocationOnly): |
| if (evinfo->method != |
| filter->u.LocationOnly.method || |
| evinfo->location != |
| filter->u.LocationOnly.location || |
| !isSameObject(env, clazz, filter->u.LocationOnly.clazz)) { |
| return JNI_FALSE; |
| } |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(FieldOnly): |
| /* Field watchpoints can be triggered from the |
| * declared class or any subclass/subinterface. |
| */ |
| if ((evinfo->u.field_access.field != |
| filter->u.FieldOnly.field) || |
| !isSameObject(env, evinfo->u.field_access.field_clazz, |
| filter->u.FieldOnly.clazz)) { |
| return JNI_FALSE; |
| } |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(ExceptionOnly): |
| /* do we want caught/uncaught exceptions */ |
| if (!((evinfo->u.exception.catch_clazz == NULL)? |
| filter->u.ExceptionOnly.uncaught : |
| filter->u.ExceptionOnly.caught)) { |
| return JNI_FALSE; |
| } |
| |
| /* do we care about exception class */ |
| if (filter->u.ExceptionOnly.exception != NULL) { |
| jclass exception = evinfo->object; |
| |
| /* do we want this exception class */ |
| if (!JNI_FUNC_PTR(env,IsInstanceOf)(env, exception, |
| filter->u.ExceptionOnly.exception)) { |
| return JNI_FALSE; |
| } |
| } |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(InstanceOnly): { |
| jobject eventInst = eventInstance(evinfo); |
| jobject filterInst = filter->u.InstanceOnly.instance; |
| /* if no error and doesn't match, don't pass |
| * filter |
| */ |
| if (eventInst != NULL && |
| !isSameObject(env, eventInst, filterInst)) { |
| return JNI_FALSE; |
| } |
| break; |
| } |
| case JDWP_REQUEST_MODIFIER(Count): { |
| JDI_ASSERT(filter->u.Count.count > 0); |
| if (--filter->u.Count.count > 0) { |
| return JNI_FALSE; |
| } |
| *shouldDelete = JNI_TRUE; |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(Conditional): |
| /*** |
| if (... filter->u.Conditional.exprID ...) { |
| return JNI_FALSE; |
| } |
| ***/ |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(ClassMatch): { |
| if (!patternStringMatch(classname, |
| filter->u.ClassMatch.classPattern)) { |
| return JNI_FALSE; |
| } |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(ClassExclude): { |
| if (patternStringMatch(classname, |
| filter->u.ClassExclude.classPattern)) { |
| return JNI_FALSE; |
| } |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(Step): |
| if (!isSameObject(env, thread, filter->u.Step.thread)) { |
| return JNI_FALSE; |
| } |
| if (!stepControl_handleStep(env, thread, clazz, method)) { |
| return JNI_FALSE; |
| } |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(SourceNameMatch): { |
| char* desiredNamePattern = filter->u.SourceNameOnly.sourceNamePattern; |
| if (searchAllSourceNames(env, clazz, |
| desiredNamePattern) != 1) { |
| /* The name isn't in the SDE; try the sourceName in the ref |
| * type |
| */ |
| char *sourceName = 0; |
| jvmtiError error = JVMTI_FUNC_PTR(gdata->jvmti,GetSourceFileName) |
| (gdata->jvmti, clazz, &sourceName); |
| if (error == JVMTI_ERROR_NONE && |
| sourceName != 0 && |
| patternStringMatch(sourceName, desiredNamePattern)) { |
| // got a hit - report the event |
| jvmtiDeallocate(sourceName); |
| break; |
| } |
| // We have no match, we have no source file name, |
| // or we got a JVM TI error. Don't report the event. |
| jvmtiDeallocate(sourceName); |
| return JNI_FALSE; |
| } |
| break; |
| } |
| |
| default: |
| EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"Invalid filter modifier"); |
| return JNI_FALSE; |
| } |
| } |
| return JNI_TRUE; |
| } |
| |
| /* Determine if this event is interesting to this handler. Do so |
| * by checking each of the handler's filters. Return false if any |
| * of the filters fail, true if the handler wants this event. |
| * Special version of filter for unloads since they don't have an |
| * event structure or a jclass. |
| * |
| * If shouldDelete is returned true, a count filter has expired |
| * and the corresponding node should be deleted. |
| */ |
| jboolean |
| eventFilterRestricted_passesUnloadFilter(JNIEnv *env, |
| char *classname, |
| HandlerNode *node, |
| jboolean *shouldDelete) |
| { |
| Filter *filter = FILTERS_ARRAY(node); |
| int i; |
| |
| *shouldDelete = JNI_FALSE; |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| |
| case JDWP_REQUEST_MODIFIER(Count): { |
| JDI_ASSERT(filter->u.Count.count > 0); |
| if (--filter->u.Count.count > 0) { |
| return JNI_FALSE; |
| } |
| *shouldDelete = JNI_TRUE; |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(ClassMatch): { |
| if (!patternStringMatch(classname, |
| filter->u.ClassMatch.classPattern)) { |
| return JNI_FALSE; |
| } |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(ClassExclude): { |
| if (patternStringMatch(classname, |
| filter->u.ClassExclude.classPattern)) { |
| return JNI_FALSE; |
| } |
| break; |
| } |
| |
| default: |
| EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"Invalid filter modifier"); |
| return JNI_FALSE; |
| } |
| } |
| return JNI_TRUE; |
| } |
| |
| /** |
| * This function returns true only if it is certain that |
| * all events for the given node in the given stack frame will |
| * be filtered. It is used to optimize stepping. (If this |
| * function returns true the stepping algorithm does not |
| * have to step through every instruction in this stack frame; |
| * instead, it can use more efficient method entry/exit |
| * events. |
| */ |
| jboolean |
| eventFilter_predictFiltering(HandlerNode *node, jclass clazz, char *classname) |
| { |
| JNIEnv *env; |
| jboolean willBeFiltered; |
| Filter *filter; |
| jboolean done; |
| int count; |
| int i; |
| |
| willBeFiltered = JNI_FALSE; |
| env = NULL; |
| filter = FILTERS_ARRAY(node); |
| count = FILTER_COUNT(node); |
| done = JNI_FALSE; |
| |
| for (i = 0; (i < count) && (!done); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(ClassOnly): |
| if ( env==NULL ) { |
| env = getEnv(); |
| } |
| if (!JNI_FUNC_PTR(env,IsAssignableFrom)(env, clazz, |
| filter->u.ClassOnly.clazz)) { |
| willBeFiltered = JNI_TRUE; |
| done = JNI_TRUE; |
| } |
| break; |
| |
| case JDWP_REQUEST_MODIFIER(Count): { |
| /* |
| * If preceding filters have determined that events will |
| * be filtered out, that is fine and we won't get here. |
| * However, the count must be decremented - even if |
| * subsequent filters will filter these events. We |
| * thus must end now unable to predict |
| */ |
| done = JNI_TRUE; |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(ClassMatch): { |
| if (!patternStringMatch(classname, |
| filter->u.ClassMatch.classPattern)) { |
| willBeFiltered = JNI_TRUE; |
| done = JNI_TRUE; |
| } |
| break; |
| } |
| |
| case JDWP_REQUEST_MODIFIER(ClassExclude): { |
| if (patternStringMatch(classname, |
| filter->u.ClassExclude.classPattern)) { |
| willBeFiltered = JNI_TRUE; |
| done = JNI_TRUE; |
| } |
| break; |
| } |
| } |
| } |
| |
| return willBeFiltered; |
| } |
| |
| /** |
| * Determine if the given breakpoint node is in the specified class. |
| */ |
| jboolean |
| eventFilterRestricted_isBreakpointInClass(JNIEnv *env, jclass clazz, |
| HandlerNode *node) |
| { |
| Filter *filter = FILTERS_ARRAY(node); |
| int i; |
| |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(LocationOnly): |
| return isSameObject(env, clazz, filter->u.LocationOnly.clazz); |
| } |
| } |
| return JNI_TRUE; /* should never come here */ |
| } |
| |
| /***** filter set-up *****/ |
| |
| jvmtiError |
| eventFilter_setConditionalFilter(HandlerNode *node, jint index, |
| jint exprID) |
| { |
| ConditionalFilter *filter = &FILTER(node, index).u.Conditional; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(Conditional); |
| filter->exprID = exprID; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setCountFilter(HandlerNode *node, jint index, |
| jint count) |
| { |
| CountFilter *filter = &FILTER(node, index).u.Count; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if (count <= 0) { |
| return JDWP_ERROR(INVALID_COUNT); |
| } else { |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(Count); |
| filter->count = count; |
| return JVMTI_ERROR_NONE; |
| } |
| } |
| |
| jvmtiError |
| eventFilter_setThreadOnlyFilter(HandlerNode *node, jint index, |
| jthread thread) |
| { |
| JNIEnv *env = getEnv(); |
| ThreadFilter *filter = &FILTER(node, index).u.ThreadOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if (NODE_EI(node) == EI_GC_FINISH) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| /* Create a thread ref that will live beyond */ |
| /* the end of this call */ |
| saveGlobalRef(env, thread, &(filter->thread)); |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(ThreadOnly); |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setLocationOnlyFilter(HandlerNode *node, jint index, |
| jclass clazz, jmethodID method, |
| jlocation location) |
| { |
| JNIEnv *env = getEnv(); |
| LocationFilter *filter = &FILTER(node, index).u.LocationOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if ((NODE_EI(node) != EI_BREAKPOINT) && |
| (NODE_EI(node) != EI_FIELD_ACCESS) && |
| (NODE_EI(node) != EI_FIELD_MODIFICATION) && |
| (NODE_EI(node) != EI_SINGLE_STEP) && |
| (NODE_EI(node) != EI_EXCEPTION)) { |
| |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| /* Create a class ref that will live beyond */ |
| /* the end of this call */ |
| saveGlobalRef(env, clazz, &(filter->clazz)); |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(LocationOnly); |
| filter->method = method; |
| filter->location = location; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setFieldOnlyFilter(HandlerNode *node, jint index, |
| jclass clazz, jfieldID field) |
| { |
| JNIEnv *env = getEnv(); |
| FieldFilter *filter = &FILTER(node, index).u.FieldOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if ((NODE_EI(node) != EI_FIELD_ACCESS) && |
| (NODE_EI(node) != EI_FIELD_MODIFICATION)) { |
| |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| /* Create a class ref that will live beyond */ |
| /* the end of this call */ |
| saveGlobalRef(env, clazz, &(filter->clazz)); |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(FieldOnly); |
| filter->field = field; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setClassOnlyFilter(HandlerNode *node, jint index, |
| jclass clazz) |
| { |
| JNIEnv *env = getEnv(); |
| ClassFilter *filter = &FILTER(node, index).u.ClassOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if ( |
| (NODE_EI(node) == EI_GC_FINISH) || |
| (NODE_EI(node) == EI_THREAD_START) || |
| (NODE_EI(node) == EI_THREAD_END)) { |
| |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| /* Create a class ref that will live beyond */ |
| /* the end of this call */ |
| saveGlobalRef(env, clazz, &(filter->clazz)); |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(ClassOnly); |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setExceptionOnlyFilter(HandlerNode *node, jint index, |
| jclass exceptionClass, |
| jboolean caught, |
| jboolean uncaught) |
| { |
| JNIEnv *env = getEnv(); |
| ExceptionFilter *filter = &FILTER(node, index).u.ExceptionOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if (NODE_EI(node) != EI_EXCEPTION) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| filter->exception = NULL; |
| if (exceptionClass != NULL) { |
| /* Create a class ref that will live beyond */ |
| /* the end of this call */ |
| saveGlobalRef(env, exceptionClass, &(filter->exception)); |
| } |
| FILTER(node, index).modifier = |
| JDWP_REQUEST_MODIFIER(ExceptionOnly); |
| filter->caught = caught; |
| filter->uncaught = uncaught; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setInstanceOnlyFilter(HandlerNode *node, jint index, |
| jobject instance) |
| { |
| JNIEnv *env = getEnv(); |
| InstanceFilter *filter = &FILTER(node, index).u.InstanceOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| filter->instance = NULL; |
| if (instance != NULL) { |
| /* Create an object ref that will live beyond |
| * the end of this call |
| */ |
| saveGlobalRef(env, instance, &(filter->instance)); |
| } |
| FILTER(node, index).modifier = |
| JDWP_REQUEST_MODIFIER(InstanceOnly); |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setClassMatchFilter(HandlerNode *node, jint index, |
| char *classPattern) |
| { |
| MatchFilter *filter = &FILTER(node, index).u.ClassMatch; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if ( |
| (NODE_EI(node) == EI_THREAD_START) || |
| (NODE_EI(node) == EI_THREAD_END)) { |
| |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| FILTER(node, index).modifier = |
| JDWP_REQUEST_MODIFIER(ClassMatch); |
| filter->classPattern = classPattern; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setClassExcludeFilter(HandlerNode *node, jint index, |
| char *classPattern) |
| { |
| MatchFilter *filter = &FILTER(node, index).u.ClassExclude; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if ( |
| (NODE_EI(node) == EI_THREAD_START) || |
| (NODE_EI(node) == EI_THREAD_END)) { |
| |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| FILTER(node, index).modifier = |
| JDWP_REQUEST_MODIFIER(ClassExclude); |
| filter->classPattern = classPattern; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| jvmtiError |
| eventFilter_setStepFilter(HandlerNode *node, jint index, |
| jthread thread, jint size, jint depth) |
| { |
| jvmtiError error; |
| JNIEnv *env = getEnv(); |
| StepFilter *filter = &FILTER(node, index).u.Step; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if (NODE_EI(node) != EI_SINGLE_STEP) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| /* Create a thread ref that will live beyond */ |
| /* the end of this call */ |
| saveGlobalRef(env, thread, &(filter->thread)); |
| error = stepControl_beginStep(env, filter->thread, size, depth, node); |
| if (error != JVMTI_ERROR_NONE) { |
| tossGlobalRef(env, &(filter->thread)); |
| return error; |
| } |
| FILTER(node, index).modifier = JDWP_REQUEST_MODIFIER(Step); |
| filter->depth = depth; |
| filter->size = size; |
| return JVMTI_ERROR_NONE; |
| } |
| |
| |
| jvmtiError |
| eventFilter_setSourceNameMatchFilter(HandlerNode *node, |
| jint index, |
| char *sourceNamePattern) { |
| SourceNameFilter *filter = &FILTER(node, index).u.SourceNameOnly; |
| if (index >= FILTER_COUNT(node)) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| if (NODE_EI(node) != EI_CLASS_PREPARE) { |
| return AGENT_ERROR_ILLEGAL_ARGUMENT; |
| } |
| |
| FILTER(node, index).modifier = |
| JDWP_REQUEST_MODIFIER(SourceNameMatch); |
| filter->sourceNamePattern = sourceNamePattern; |
| return JVMTI_ERROR_NONE; |
| |
| } |
| |
| /***** JVMTI event enabling / disabling *****/ |
| |
| /** |
| * Return the Filter that is of the specified type (modifier). |
| * Return NULL if not found. |
| */ |
| static Filter * |
| findFilter(HandlerNode *node, jint modifier) |
| { |
| int i; |
| Filter *filter; |
| for (i = 0, filter = FILTERS_ARRAY(node); |
| i <FILTER_COUNT(node); |
| i++, filter++) { |
| if (filter->modifier == modifier) { |
| return filter; |
| } |
| } |
| return NULL; |
| } |
| |
| /** |
| * Determine if the specified breakpoint node is in the |
| * same location as the LocationFilter passed in arg. |
| * |
| * This is a match function called by a |
| * eventHandlerRestricted_iterator invokation. |
| */ |
| static jboolean |
| matchBreakpoint(JNIEnv *env, HandlerNode *node, void *arg) |
| { |
| LocationFilter *goal = (LocationFilter *)arg; |
| Filter *filter = FILTERS_ARRAY(node); |
| int i; |
| |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(LocationOnly): { |
| LocationFilter *trial = &(filter->u.LocationOnly); |
| if (trial->method == goal->method && |
| trial->location == goal->location && |
| isSameObject(env, trial->clazz, goal->clazz)) { |
| return JNI_TRUE; |
| } |
| } |
| } |
| } |
| return JNI_FALSE; |
| } |
| |
| /** |
| * Set a breakpoint if this is the first one at this location. |
| */ |
| static jvmtiError |
| setBreakpoint(HandlerNode *node) |
| { |
| jvmtiError error = JVMTI_ERROR_NONE; |
| Filter *filter; |
| |
| filter = findFilter(node, JDWP_REQUEST_MODIFIER(LocationOnly)); |
| if (filter == NULL) { |
| /* bp event with no location filter */ |
| error = AGENT_ERROR_INTERNAL; |
| } else { |
| LocationFilter *lf = &(filter->u.LocationOnly); |
| |
| /* if this is the first handler for this |
| * location, set bp at JVMTI level |
| */ |
| if (!eventHandlerRestricted_iterator( |
| EI_BREAKPOINT, matchBreakpoint, lf)) { |
| LOG_LOC(("SetBreakpoint at location: method=%p,location=%d", |
| lf->method, (int)lf->location)); |
| error = JVMTI_FUNC_PTR(gdata->jvmti,SetBreakpoint) |
| (gdata->jvmti, lf->method, lf->location); |
| } |
| } |
| return error; |
| } |
| |
| /** |
| * Clear a breakpoint if this is the last one at this location. |
| */ |
| static jvmtiError |
| clearBreakpoint(HandlerNode *node) |
| { |
| jvmtiError error = JVMTI_ERROR_NONE; |
| Filter *filter; |
| |
| filter = findFilter(node, JDWP_REQUEST_MODIFIER(LocationOnly)); |
| if (filter == NULL) { |
| /* bp event with no location filter */ |
| error = AGENT_ERROR_INTERNAL; |
| } else { |
| LocationFilter *lf = &(filter->u.LocationOnly); |
| |
| /* if this is the last handler for this |
| * location, clear bp at JVMTI level |
| */ |
| if (!eventHandlerRestricted_iterator( |
| EI_BREAKPOINT, matchBreakpoint, lf)) { |
| LOG_LOC(("ClearBreakpoint at location: method=%p,location=%d", |
| lf->method, (int)lf->location)); |
| error = JVMTI_FUNC_PTR(gdata->jvmti,ClearBreakpoint) |
| (gdata->jvmti, lf->method, lf->location); |
| } |
| } |
| return error; |
| } |
| |
| /** |
| * Return true if a breakpoint is set at the specified location. |
| */ |
| jboolean |
| isBreakpointSet(jclass clazz, jmethodID method, jlocation location) |
| { |
| LocationFilter lf; |
| |
| lf.clazz = clazz; |
| lf.method = method; |
| lf.location = location; |
| |
| return eventHandlerRestricted_iterator(EI_BREAKPOINT, |
| matchBreakpoint, &lf); |
| } |
| |
| /** |
| * Determine if the specified watchpoint node has the |
| * same field as the FieldFilter passed in arg. |
| * |
| * This is a match function called by a |
| * eventHandlerRestricted_iterator invokation. |
| */ |
| static jboolean |
| matchWatchpoint(JNIEnv *env, HandlerNode *node, void *arg) |
| { |
| FieldFilter *goal = (FieldFilter *)arg; |
| Filter *filter = FILTERS_ARRAY(node); |
| int i; |
| |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(FieldOnly): { |
| FieldFilter *trial = &(filter->u.FieldOnly); |
| if (trial->field == goal->field && |
| isSameObject(env, trial->clazz, goal->clazz)) { |
| return JNI_TRUE; |
| } |
| } |
| } |
| } |
| return JNI_FALSE; |
| } |
| |
| /** |
| * Set a watchpoint if this is the first one on this field. |
| */ |
| static jvmtiError |
| setWatchpoint(HandlerNode *node) |
| { |
| jvmtiError error = JVMTI_ERROR_NONE; |
| Filter *filter; |
| |
| filter = findFilter(node, JDWP_REQUEST_MODIFIER(FieldOnly)); |
| if (filter == NULL) { |
| /* event with no field filter */ |
| error = AGENT_ERROR_INTERNAL; |
| } else { |
| FieldFilter *ff = &(filter->u.FieldOnly); |
| |
| /* if this is the first handler for this |
| * field, set wp at JVMTI level |
| */ |
| if (!eventHandlerRestricted_iterator( |
| NODE_EI(node), matchWatchpoint, ff)) { |
| error = (NODE_EI(node) == EI_FIELD_ACCESS) ? |
| JVMTI_FUNC_PTR(gdata->jvmti,SetFieldAccessWatch) |
| (gdata->jvmti, ff->clazz, ff->field) : |
| JVMTI_FUNC_PTR(gdata->jvmti,SetFieldModificationWatch) |
| (gdata->jvmti, ff->clazz, ff->field); |
| } |
| } |
| return error; |
| } |
| |
| /** |
| * Clear a watchpoint if this is the last one on this field. |
| */ |
| static jvmtiError |
| clearWatchpoint(HandlerNode *node) |
| { |
| jvmtiError error = JVMTI_ERROR_NONE; |
| Filter *filter; |
| |
| filter = findFilter(node, JDWP_REQUEST_MODIFIER(FieldOnly)); |
| if (filter == NULL) { |
| /* event with no field filter */ |
| error = AGENT_ERROR_INTERNAL; |
| } else { |
| FieldFilter *ff = &(filter->u.FieldOnly); |
| |
| /* if this is the last handler for this |
| * field, clear wp at JVMTI level |
| */ |
| if (!eventHandlerRestricted_iterator( |
| NODE_EI(node), matchWatchpoint, ff)) { |
| error = (NODE_EI(node) == EI_FIELD_ACCESS) ? |
| JVMTI_FUNC_PTR(gdata->jvmti,ClearFieldAccessWatch) |
| (gdata->jvmti, ff->clazz, ff->field) : |
| JVMTI_FUNC_PTR(gdata->jvmti,ClearFieldModificationWatch) |
| (gdata->jvmti, ff->clazz, ff->field); |
| } |
| } |
| return error; |
| } |
| |
| /** |
| * Determine the thread this node is filtered on. |
| * NULL if not thread filtered. |
| */ |
| static jthread |
| requestThread(HandlerNode *node) |
| { |
| int i; |
| Filter *filter = FILTERS_ARRAY(node); |
| |
| for (i = 0; i < FILTER_COUNT(node); ++i, ++filter) { |
| switch (filter->modifier) { |
| case JDWP_REQUEST_MODIFIER(ThreadOnly): |
| return filter->u.ThreadOnly.thread; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Determine if the specified node has a |
| * thread filter with the thread passed in arg. |
| * |
| * This is a match function called by a |
| * eventHandlerRestricted_iterator invokation. |
| */ |
| static jboolean |
| matchThread(JNIEnv *env, HandlerNode *node, void *arg) |
| { |
| jthread goalThread = (jthread)arg; |
| jthread reqThread = requestThread(node); |
| |
| /* If the event's thread and the passed thread are the same |
| * (or both are NULL), we have a match. |
| */ |
| return isSameObject(env, reqThread, goalThread); |
| } |
| |
| /** |
| * Do any enabling of events (including setting breakpoints etc) |
| * needed to get the events requested by this handler node. |
| */ |
| static jvmtiError |
| enableEvents(HandlerNode *node) |
| { |
| jvmtiError error = JVMTI_ERROR_NONE; |
| |
| switch (NODE_EI(node)) { |
| /* The stepping code directly enables/disables stepping as |
| * necessary |
| */ |
| case EI_SINGLE_STEP: |
| /* Internal thread event handlers are always present |
| * (hardwired in the event hook), so we don't change the |
| * notification mode here. |
| */ |
| case EI_THREAD_START: |
| case EI_THREAD_END: |
| case EI_VM_INIT: |
| case EI_VM_DEATH: |
| case EI_CLASS_PREPARE: |
| case EI_GC_FINISH: |
| return error; |
| |
| case EI_FIELD_ACCESS: |
| case EI_FIELD_MODIFICATION: |
| error = setWatchpoint(node); |
| break; |
| |
| case EI_BREAKPOINT: |
| error = setBreakpoint(node); |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* Don't globally enable if the above failed */ |
| if (error == JVMTI_ERROR_NONE) { |
| jthread thread = requestThread(node); |
| |
| /* If this is the first request of it's kind on this |
| * thread (or all threads (thread == NULL)) then enable |
| * these events on this thread. |
| */ |
| if (!eventHandlerRestricted_iterator( |
| NODE_EI(node), matchThread, thread)) { |
| error = threadControl_setEventMode(JVMTI_ENABLE, |
| NODE_EI(node), thread); |
| } |
| } |
| return error; |
| } |
| |
| /** |
| * Do any disabling of events (including clearing breakpoints etc) |
| * needed to no longer get the events requested by this handler node. |
| */ |
| static jvmtiError |
| disableEvents(HandlerNode *node) |
| { |
| jvmtiError error = JVMTI_ERROR_NONE; |
| jvmtiError error2 = JVMTI_ERROR_NONE; |
| jthread thread; |
| |
| |
| switch (NODE_EI(node)) { |
| /* The stepping code directly enables/disables stepping as |
| * necessary |
| */ |
| case EI_SINGLE_STEP: |
| /* Internal thread event handlers are always present |
| * (hardwired in the event hook), so we don't change the |
| * notification mode here. |
| */ |
| case EI_THREAD_START: |
| case EI_THREAD_END: |
| case EI_VM_INIT: |
| case EI_VM_DEATH: |
| case EI_CLASS_PREPARE: |
| case EI_GC_FINISH: |
| return error; |
| |
| case EI_FIELD_ACCESS: |
| case EI_FIELD_MODIFICATION: |
| error = clearWatchpoint(node); |
| break; |
| |
| case EI_BREAKPOINT: |
| error = clearBreakpoint(node); |
| break; |
| |
| default: |
| break; |
| } |
| |
| thread = requestThread(node); |
| |
| /* If this is the last request of it's kind on this thread |
| * (or all threads (thread == NULL)) then disable these |
| * events on this thread. |
| * |
| * Disable even if the above caused an error |
| */ |
| if (!eventHandlerRestricted_iterator(NODE_EI(node), matchThread, thread)) { |
| error2 = threadControl_setEventMode(JVMTI_DISABLE, |
| NODE_EI(node), thread); |
| } |
| return error != JVMTI_ERROR_NONE? error : error2; |
| } |
| |
| |
| /***** filter (and event) installation and deinstallation *****/ |
| |
| /** |
| * Make the set of event filters that correspond with this |
| * node active (including enabling the corresponding events). |
| */ |
| jvmtiError |
| eventFilterRestricted_install(HandlerNode *node) |
| { |
| return enableEvents(node); |
| } |
| |
| /** |
| * Make the set of event filters that correspond with this |
| * node inactive (including disabling the corresponding events |
| * and freeing resources). |
| */ |
| jvmtiError |
| eventFilterRestricted_deinstall(HandlerNode *node) |
| { |
| jvmtiError error1, error2; |
| |
| error1 = disableEvents(node); |
| error2 = clearFilters(node); |
| |
| return error1 != JVMTI_ERROR_NONE? error1 : error2; |
| } |