/* * Copyright (c) 1998, 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. */ #include <ctype.h> // ANDROID-CHANGED: Include time.h so we can use clock_gettime to implement milliTime. #include <time.h> #include "util.h" #include "transport.h" #include "eventHandler.h" #include "threadControl.h" #include "outStream.h" #include "inStream.h" #include "invoker.h" /* Global data area */ BackendGlobalData *gdata = NULL; /* Forward declarations */ static jboolean isInterface(jclass clazz); static jboolean isArrayClass(jclass clazz); static char * getPropertyUTF8(JNIEnv *env, char *propertyName); // ANDROID-CHANGED: Implement a helper to get the current time in milliseconds according to // CLOCK_MONOTONIC. jlong milliTime(void) { struct timespec now; memset(&now, 0, sizeof(now)); (void)clock_gettime(CLOCK_MONOTONIC, &now); return ((jlong)now.tv_sec) * 1000LL + ((jlong)now.tv_nsec) / 1000000LL; } /* Save an object reference for use later (create a NewGlobalRef) */ void saveGlobalRef(JNIEnv *env, jobject obj, jobject *pobj) { jobject newobj; if ( pobj == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"saveGlobalRef pobj"); } if ( *pobj != NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"saveGlobalRef *pobj"); } if ( env == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"saveGlobalRef env"); } if ( obj == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"saveGlobalRef obj"); } newobj = JNI_FUNC_PTR(env,NewGlobalRef)(env, obj); if ( newobj == NULL ) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"NewGlobalRef"); } *pobj = newobj; } /* Toss a previously saved object reference */ void tossGlobalRef(JNIEnv *env, jobject *pobj) { jobject obj; if ( pobj == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"tossGlobalRef pobj"); } obj = *pobj; if ( env == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"tossGlobalRef env"); } if ( obj == NULL ) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"tossGlobalRef obj"); } JNI_FUNC_PTR(env,DeleteGlobalRef)(env, obj); *pobj = NULL; } static jclass findClass(JNIEnv *env, const char * name) { jclass x; if ( env == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"findClass env"); } if ( name == NULL || name[0] == 0 ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"findClass name"); } x = JNI_FUNC_PTR(env,FindClass)(env, name); if (x == NULL) { ERROR_MESSAGE(("JDWP Can't find class %s", name)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } if ( JNI_FUNC_PTR(env,ExceptionOccurred)(env) ) { ERROR_MESSAGE(("JDWP Exception occurred finding class %s", name)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } return x; } static jmethodID getMethod(JNIEnv *env, jclass clazz, const char * name, const char *signature) { jmethodID method; if ( env == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getMethod env"); } if ( clazz == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getMethod clazz"); } if ( name == NULL || name[0] == 0 ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getMethod name"); } if ( signature == NULL || signature[0] == 0 ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getMethod signature"); } method = JNI_FUNC_PTR(env,GetMethodID)(env, clazz, name, signature); if (method == NULL) { ERROR_MESSAGE(("JDWP Can't find method %s with signature %s", name, signature)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } if ( JNI_FUNC_PTR(env,ExceptionOccurred)(env) ) { ERROR_MESSAGE(("JDWP Exception occurred finding method %s with signature %s", name, signature)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } return method; } static jmethodID getStaticMethod(JNIEnv *env, jclass clazz, const char * name, const char *signature) { jmethodID method; if ( env == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getStaticMethod env"); } if ( clazz == NULL ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getStaticMethod clazz"); } if ( name == NULL || name[0] == 0 ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getStaticMethod name"); } if ( signature == NULL || signature[0] == 0 ) { EXIT_ERROR(AGENT_ERROR_ILLEGAL_ARGUMENT,"getStaticMethod signature"); } method = JNI_FUNC_PTR(env,GetStaticMethodID)(env, clazz, name, signature); if (method == NULL) { ERROR_MESSAGE(("JDWP Can't find method %s with signature %s", name, signature)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } if ( JNI_FUNC_PTR(env,ExceptionOccurred)(env) ) { ERROR_MESSAGE(("JDWP Exception occurred finding method %s with signature %s", name, signature)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } return method; } void util_initialize(JNIEnv *env) { WITH_LOCAL_REFS(env, 6) { jvmtiError error; jclass localClassClass; jclass localThreadClass; jclass localThreadGroupClass; jclass localClassLoaderClass; jclass localStringClass; jclass localSystemClass; jclass localPropertiesClass; jclass localVMSupportClass; jobject localAgentProperties; jmethodID getAgentProperties; jint groupCount; jthreadGroup *groups; jthreadGroup localSystemThreadGroup; /* Find some standard classes */ localClassClass = findClass(env,"java/lang/Class"); localThreadClass = findClass(env,"java/lang/Thread"); localThreadGroupClass = findClass(env,"java/lang/ThreadGroup"); localClassLoaderClass = findClass(env,"java/lang/ClassLoader"); localStringClass = findClass(env,"java/lang/String"); localSystemClass = findClass(env,"java/lang/System"); localPropertiesClass = findClass(env,"java/util/Properties"); /* Save references */ saveGlobalRef(env, localClassClass, &(gdata->classClass)); saveGlobalRef(env, localThreadClass, &(gdata->threadClass)); saveGlobalRef(env, localThreadGroupClass, &(gdata->threadGroupClass)); saveGlobalRef(env, localClassLoaderClass, &(gdata->classLoaderClass)); saveGlobalRef(env, localStringClass, &(gdata->stringClass)); saveGlobalRef(env, localSystemClass, &(gdata->systemClass)); /* Find some standard methods */ gdata->threadConstructor = getMethod(env, gdata->threadClass, "<init>", "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V"); gdata->threadSetDaemon = getMethod(env, gdata->threadClass, "setDaemon", "(Z)V"); gdata->threadResume = getMethod(env, gdata->threadClass, "resume", "()V"); gdata->systemGetProperty = getStaticMethod(env, gdata->systemClass, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); gdata->setProperty = getMethod(env, localPropertiesClass, "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;"); /* Find the system thread group */ groups = NULL; groupCount = 0; error = JVMTI_FUNC_PTR(gdata->jvmti,GetTopThreadGroups) (gdata->jvmti, &groupCount, &groups); if (error != JVMTI_ERROR_NONE ) { EXIT_ERROR(error, "Can't get system thread group"); } if ( groupCount == 0 ) { EXIT_ERROR(AGENT_ERROR_NULL_POINTER, "Can't get system thread group"); } localSystemThreadGroup = groups[0]; saveGlobalRef(env, localSystemThreadGroup, &(gdata->systemThreadGroup)); /* Get some basic Java property values we will need at some point */ gdata->property_java_version = getPropertyUTF8(env, "java.version"); gdata->property_java_vm_name = getPropertyUTF8(env, "java.vm.name"); // ANDROID-CHANGED: Android doesn't provide the 'java.vm.info' property. Just provide the // rest of the agent with an empty string to use for it. gdata->property_java_vm_info = jvmtiAllocate(1); gdata->property_java_vm_info[0] = '\0'; gdata->property_java_class_path = getPropertyUTF8(env, "java.class.path"); // ANDROID-CHANGED: Android uses java.boot.class.path to store the bootclasspath. gdata->property_sun_boot_class_path = getPropertyUTF8(env, "java.boot.class.path"); // ANDROID-CHANGED: Android uses java.library.path to store all library path information. gdata->property_sun_boot_library_path = getPropertyUTF8(env, "java.library.path"); gdata->property_path_separator = getPropertyUTF8(env, "path.separator"); gdata->property_user_dir = getPropertyUTF8(env, "user.dir"); /* Get agent properties: invoke sun.misc.VMSupport.getAgentProperties */ localVMSupportClass = JNI_FUNC_PTR(env,FindClass) (env, "sun/misc/VMSupport"); if (localVMSupportClass == NULL) { gdata->agent_properties = NULL; if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); } } else { getAgentProperties = getStaticMethod(env, localVMSupportClass, "getAgentProperties", "()Ljava/util/Properties;"); localAgentProperties = JNI_FUNC_PTR(env,CallStaticObjectMethod) (env, localVMSupportClass, getAgentProperties); saveGlobalRef(env, localAgentProperties, &(gdata->agent_properties)); if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); EXIT_ERROR(AGENT_ERROR_INTERNAL, "Exception occurred calling sun.misc.VMSupport.getAgentProperties"); } } } END_WITH_LOCAL_REFS(env); } void util_reset(void) { } jboolean isObjectTag(jbyte tag) { return (tag == JDWP_TAG(OBJECT)) || (tag == JDWP_TAG(STRING)) || (tag == JDWP_TAG(THREAD)) || (tag == JDWP_TAG(THREAD_GROUP)) || (tag == JDWP_TAG(CLASS_LOADER)) || (tag == JDWP_TAG(CLASS_OBJECT)) || (tag == JDWP_TAG(ARRAY)); } jbyte specificTypeKey(JNIEnv *env, jobject object) { if (object == NULL) { return JDWP_TAG(OBJECT); } else if (JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->stringClass)) { return JDWP_TAG(STRING); } else if (JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->threadClass)) { return JDWP_TAG(THREAD); } else if (JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->threadGroupClass)) { return JDWP_TAG(THREAD_GROUP); } else if (JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->classLoaderClass)) { return JDWP_TAG(CLASS_LOADER); } else if (JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->classClass)) { return JDWP_TAG(CLASS_OBJECT); } else { jboolean classIsArray; WITH_LOCAL_REFS(env, 1) { jclass clazz; clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, object); classIsArray = isArrayClass(clazz); } END_WITH_LOCAL_REFS(env); return (classIsArray ? JDWP_TAG(ARRAY) : JDWP_TAG(OBJECT)); } } static void writeFieldValue(JNIEnv *env, PacketOutputStream *out, jobject object, jfieldID field) { jclass clazz; char *signature = NULL; jvmtiError error; jbyte typeKey; clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, object); error = fieldSignature(clazz, field, NULL, &signature, NULL); if (error != JVMTI_ERROR_NONE) { outStream_setError(out, map2jdwpError(error)); return; } typeKey = signature[0]; jvmtiDeallocate(signature); /* * For primitive types, the type key is bounced back as is. Objects * are handled in the switch statement below. */ if ((typeKey != JDWP_TAG(OBJECT)) && (typeKey != JDWP_TAG(ARRAY))) { (void)outStream_writeByte(out, typeKey); } switch (typeKey) { case JDWP_TAG(OBJECT): case JDWP_TAG(ARRAY): { jobject value = JNI_FUNC_PTR(env,GetObjectField)(env, object, field); (void)outStream_writeByte(out, specificTypeKey(env, value)); (void)outStream_writeObjectRef(env, out, value); break; } case JDWP_TAG(BYTE): (void)outStream_writeByte(out, JNI_FUNC_PTR(env,GetByteField)(env, object, field)); break; case JDWP_TAG(CHAR): (void)outStream_writeChar(out, JNI_FUNC_PTR(env,GetCharField)(env, object, field)); break; case JDWP_TAG(FLOAT): (void)outStream_writeFloat(out, JNI_FUNC_PTR(env,GetFloatField)(env, object, field)); break; case JDWP_TAG(DOUBLE): (void)outStream_writeDouble(out, JNI_FUNC_PTR(env,GetDoubleField)(env, object, field)); break; case JDWP_TAG(INT): (void)outStream_writeInt(out, JNI_FUNC_PTR(env,GetIntField)(env, object, field)); break; case JDWP_TAG(LONG): (void)outStream_writeLong(out, JNI_FUNC_PTR(env,GetLongField)(env, object, field)); break; case JDWP_TAG(SHORT): (void)outStream_writeShort(out, JNI_FUNC_PTR(env,GetShortField)(env, object, field)); break; case JDWP_TAG(BOOLEAN): (void)outStream_writeBoolean(out, JNI_FUNC_PTR(env,GetBooleanField)(env, object, field)); break; } } static void writeStaticFieldValue(JNIEnv *env, PacketOutputStream *out, jclass clazz, jfieldID field) { jvmtiError error; char *signature = NULL; jbyte typeKey; error = fieldSignature(clazz, field, NULL, &signature, NULL); if (error != JVMTI_ERROR_NONE) { outStream_setError(out, map2jdwpError(error)); return; } typeKey = signature[0]; jvmtiDeallocate(signature); /* * For primitive types, the type key is bounced back as is. Objects * are handled in the switch statement below. */ if ((typeKey != JDWP_TAG(OBJECT)) && (typeKey != JDWP_TAG(ARRAY))) { (void)outStream_writeByte(out, typeKey); } switch (typeKey) { case JDWP_TAG(OBJECT): case JDWP_TAG(ARRAY): { jobject value = JNI_FUNC_PTR(env,GetStaticObjectField)(env, clazz, field); (void)outStream_writeByte(out, specificTypeKey(env, value)); (void)outStream_writeObjectRef(env, out, value); break; } case JDWP_TAG(BYTE): (void)outStream_writeByte(out, JNI_FUNC_PTR(env,GetStaticByteField)(env, clazz, field)); break; case JDWP_TAG(CHAR): (void)outStream_writeChar(out, JNI_FUNC_PTR(env,GetStaticCharField)(env, clazz, field)); break; case JDWP_TAG(FLOAT): (void)outStream_writeFloat(out, JNI_FUNC_PTR(env,GetStaticFloatField)(env, clazz, field)); break; case JDWP_TAG(DOUBLE): (void)outStream_writeDouble(out, JNI_FUNC_PTR(env,GetStaticDoubleField)(env, clazz, field)); break; case JDWP_TAG(INT): (void)outStream_writeInt(out, JNI_FUNC_PTR(env,GetStaticIntField)(env, clazz, field)); break; case JDWP_TAG(LONG): (void)outStream_writeLong(out, JNI_FUNC_PTR(env,GetStaticLongField)(env, clazz, field)); break; case JDWP_TAG(SHORT): (void)outStream_writeShort(out, JNI_FUNC_PTR(env,GetStaticShortField)(env, clazz, field)); break; case JDWP_TAG(BOOLEAN): (void)outStream_writeBoolean(out, JNI_FUNC_PTR(env,GetStaticBooleanField)(env, clazz, field)); break; } } void sharedGetFieldValues(PacketInputStream *in, PacketOutputStream *out, jboolean isStatic) { JNIEnv *env = getEnv(); jint length; jobject object; jclass clazz; object = NULL; clazz = NULL; if (isStatic) { clazz = inStream_readClassRef(env, in); } else { object = inStream_readObjectRef(env, in); } length = inStream_readInt(in); if (inStream_error(in)) { return; } WITH_LOCAL_REFS(env, length + 1) { /* +1 for class with instance fields */ int i; (void)outStream_writeInt(out, length); for (i = 0; (i < length) && !outStream_error(out); i++) { jfieldID field = inStream_readFieldID(in); if (isStatic) { writeStaticFieldValue(env, out, clazz, field); } else { writeFieldValue(env, out, object, field); } } } END_WITH_LOCAL_REFS(env); } jboolean sharedInvoke(PacketInputStream *in, PacketOutputStream *out) { jvalue *arguments = NULL; jint options; jvmtiError error; jbyte invokeType; jclass clazz; jmethodID method; jint argumentCount; jobject instance; jthread thread; JNIEnv *env; /* * Instance methods start with the instance, thread and class, * and statics and constructors start with the class and then the * thread. */ env = getEnv(); if (inStream_command(in) == JDWP_COMMAND(ObjectReference, InvokeMethod)) { instance = inStream_readObjectRef(env, in); thread = inStream_readThreadRef(env, in); clazz = inStream_readClassRef(env, in); } else { /* static method or constructor */ instance = NULL; clazz = inStream_readClassRef(env, in); thread = inStream_readThreadRef(env, in); } /* * ... and the rest of the packet is identical for all commands */ method = inStream_readMethodID(in); argumentCount = inStream_readInt(in); if (inStream_error(in)) { return JNI_TRUE; } /* If count == 0, don't try and allocate 0 bytes, you'll get NULL */ if ( argumentCount > 0 ) { int i; /*LINTED*/ arguments = jvmtiAllocate(argumentCount * (jint)sizeof(*arguments)); if (arguments == NULL) { outStream_setError(out, JDWP_ERROR(OUT_OF_MEMORY)); return JNI_TRUE; } for (i = 0; (i < argumentCount) && !inStream_error(in); i++) { arguments[i] = inStream_readValue(in, NULL); } if (inStream_error(in)) { return JNI_TRUE; } } options = inStream_readInt(in); if (inStream_error(in)) { if ( arguments != NULL ) { jvmtiDeallocate(arguments); } return JNI_TRUE; } if (inStream_command(in) == JDWP_COMMAND(ClassType, NewInstance)) { invokeType = INVOKE_CONSTRUCTOR; } else if (inStream_command(in) == JDWP_COMMAND(ClassType, InvokeMethod)) { invokeType = INVOKE_STATIC; } else if (inStream_command(in) == JDWP_COMMAND(InterfaceType, InvokeMethod)) { invokeType = INVOKE_STATIC; } else if (inStream_command(in) == JDWP_COMMAND(ObjectReference, InvokeMethod)) { invokeType = INVOKE_INSTANCE; } else { outStream_setError(out, JDWP_ERROR(INTERNAL)); if ( arguments != NULL ) { jvmtiDeallocate(arguments); } return JNI_TRUE; } /* * Request the invoke. If there are no errors in the request, * the interrupting thread will actually do the invoke and a * reply will be generated subsequently, so we don't reply here. */ error = invoker_requestInvoke(invokeType, (jbyte)options, inStream_id(in), thread, clazz, method, instance, arguments, argumentCount); if (error != JVMTI_ERROR_NONE) { outStream_setError(out, map2jdwpError(error)); if ( arguments != NULL ) { jvmtiDeallocate(arguments); } return JNI_TRUE; } return JNI_FALSE; /* Don't reply */ } jint uniqueID(void) { static jint currentID = 0; // ANDROID-CHANGED: on android we sometimes need to share these id's with DDMS traffic that is // multiplexed on the same connection. Since we don't have any way to know which id's are taken // by DDMS we will instead partition the ids between them. All positive ids (sign-bit == 0) are // reserved for libjdwp. DDMS will take ids with sign-bit == 1. This condition is not expected // to ever be true on a normal debugging session. if (currentID < 0) { currentID = 0; } return currentID++; } int filterDebugThreads(jthread *threads, int count) { int i; int current; /* Squish out all of the debugger-spawned threads */ for (i = 0, current = 0; i < count; i++) { jthread thread = threads[i]; if (!threadControl_isDebugThread(thread)) { if (i > current) { threads[current] = thread; } current++; } } return current; } jbyte referenceTypeTag(jclass clazz) { jbyte tag; if (isInterface(clazz)) { tag = JDWP_TYPE_TAG(INTERFACE); } else if (isArrayClass(clazz)) { tag = JDWP_TYPE_TAG(ARRAY); } else { tag = JDWP_TYPE_TAG(CLASS); } return tag; } /** * Get field modifiers */ jvmtiError fieldModifiers(jclass clazz, jfieldID field, jint *pmodifiers) { jvmtiError error; *pmodifiers = 0; error = JVMTI_FUNC_PTR(gdata->jvmti,GetFieldModifiers) (gdata->jvmti, clazz, field, pmodifiers); return error; } /** * Get method modifiers */ jvmtiError methodModifiers(jmethodID method, jint *pmodifiers) { jvmtiError error; *pmodifiers = 0; error = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodModifiers) (gdata->jvmti, method, pmodifiers); return error; } /* Returns a local ref to the declaring class for a method, or NULL. */ jvmtiError methodClass(jmethodID method, jclass *pclazz) { jvmtiError error; *pclazz = NULL; error = FUNC_PTR(gdata->jvmti,GetMethodDeclaringClass) (gdata->jvmti, method, pclazz); return error; } /* Returns a local ref to the declaring class for a method, or NULL. */ jvmtiError methodLocation(jmethodID method, jlocation *ploc1, jlocation *ploc2) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodLocation) (gdata->jvmti, method, ploc1, ploc2); return error; } /** * Get method signature */ jvmtiError methodSignature(jmethodID method, char **pname, char **psignature, char **pgeneric_signature) { jvmtiError error; char *name = NULL; char *signature = NULL; char *generic_signature = NULL; error = FUNC_PTR(gdata->jvmti,GetMethodName) (gdata->jvmti, method, &name, &signature, &generic_signature); if ( pname != NULL ) { *pname = name; } else if ( name != NULL ) { jvmtiDeallocate(name); } if ( psignature != NULL ) { *psignature = signature; } else if ( signature != NULL ) { jvmtiDeallocate(signature); } if ( pgeneric_signature != NULL ) { *pgeneric_signature = generic_signature; } else if ( generic_signature != NULL ) { jvmtiDeallocate(generic_signature); } return error; } /* * Get the return type key of the method * V or B C D F I J S Z L [ */ jvmtiError methodReturnType(jmethodID method, char *typeKey) { char *signature; jvmtiError error; signature = NULL; error = methodSignature(method, NULL, &signature, NULL); if (error == JVMTI_ERROR_NONE) { if (signature == NULL ) { error = AGENT_ERROR_INVALID_TAG; } else { char * xx; xx = strchr(signature, ')'); if (xx == NULL || *(xx + 1) == 0) { error = AGENT_ERROR_INVALID_TAG; } else { *typeKey = *(xx + 1); } jvmtiDeallocate(signature); } } return error; } /** * Return class loader for a class (must be inside a WITH_LOCAL_REFS) */ jvmtiError classLoader(jclass clazz, jobject *pclazz) { jvmtiError error; *pclazz = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassLoader) (gdata->jvmti, clazz, pclazz); return error; } /** * Get field signature */ jvmtiError fieldSignature(jclass clazz, jfieldID field, char **pname, char **psignature, char **pgeneric_signature) { jvmtiError error; char *name = NULL; char *signature = NULL; char *generic_signature = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetFieldName) (gdata->jvmti, clazz, field, &name, &signature, &generic_signature); if ( pname != NULL ) { *pname = name; } else if ( name != NULL ) { jvmtiDeallocate(name); } if ( psignature != NULL ) { *psignature = signature; } else if ( signature != NULL ) { jvmtiDeallocate(signature); } if ( pgeneric_signature != NULL ) { *pgeneric_signature = generic_signature; } else if ( generic_signature != NULL ) { jvmtiDeallocate(generic_signature); } return error; } JNIEnv * getEnv(void) { JNIEnv *env = NULL; jint rc; rc = FUNC_PTR(gdata->jvm,GetEnv) (gdata->jvm, (void **)&env, JNI_VERSION_1_2); if (rc != JNI_OK) { ERROR_MESSAGE(("JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = %d", rc)); EXIT_ERROR(AGENT_ERROR_NO_JNI_ENV,NULL); } return env; } jvmtiError spawnNewThread(jvmtiStartFunction func, void *arg, char *name) { JNIEnv *env = getEnv(); jvmtiError error; LOG_MISC(("Spawning new thread: %s", name)); WITH_LOCAL_REFS(env, 3) { jthread thread; jstring nameString; nameString = JNI_FUNC_PTR(env,NewStringUTF)(env, name); if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); error = AGENT_ERROR_OUT_OF_MEMORY; goto err; } thread = JNI_FUNC_PTR(env,NewObject) (env, gdata->threadClass, gdata->threadConstructor, gdata->systemThreadGroup, nameString); if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); error = AGENT_ERROR_OUT_OF_MEMORY; goto err; } /* * Make the debugger thread a daemon */ JNI_FUNC_PTR(env,CallVoidMethod) (env, thread, gdata->threadSetDaemon, JNI_TRUE); if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); error = AGENT_ERROR_JNI_EXCEPTION; goto err; } error = threadControl_addDebugThread(thread); if (error == JVMTI_ERROR_NONE) { /* * Debugger threads need cycles in all sorts of strange * situations (e.g. infinite cpu-bound loops), so give the * thread a high priority. Note that if the VM has an application * thread running at the max priority, there is still a chance * that debugger threads will be starved. (There needs to be * a way to give debugger threads a priority higher than any * application thread). */ error = JVMTI_FUNC_PTR(gdata->jvmti,RunAgentThread) (gdata->jvmti, thread, func, arg, JVMTI_THREAD_MAX_PRIORITY); } err: ; } END_WITH_LOCAL_REFS(env); return error; } jvmtiError jvmtiGetCapabilities(jvmtiCapabilities *caps) { if ( gdata->vmDead ) { return AGENT_ERROR_VM_DEAD; } if (!gdata->haveCachedJvmtiCapabilities) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,GetCapabilities) (gdata->jvmti, &(gdata->cachedJvmtiCapabilities)); if (error != JVMTI_ERROR_NONE) { return error; } gdata->haveCachedJvmtiCapabilities = JNI_TRUE; } *caps = gdata->cachedJvmtiCapabilities; return JVMTI_ERROR_NONE; } static jint jvmtiVersion(void) { if (gdata->cachedJvmtiVersion == 0) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,GetVersionNumber) (gdata->jvmti, &(gdata->cachedJvmtiVersion)); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on getting the JVMTI version number"); } } return gdata->cachedJvmtiVersion; } jint jvmtiMajorVersion(void) { return (jvmtiVersion() & JVMTI_VERSION_MASK_MAJOR) >> JVMTI_VERSION_SHIFT_MAJOR; } jint jvmtiMinorVersion(void) { return (jvmtiVersion() & JVMTI_VERSION_MASK_MINOR) >> JVMTI_VERSION_SHIFT_MINOR; } jint jvmtiMicroVersion(void) { return (jvmtiVersion() & JVMTI_VERSION_MASK_MICRO) >> JVMTI_VERSION_SHIFT_MICRO; } jboolean canSuspendResumeThreadLists(void) { jvmtiError error; jvmtiCapabilities cap; error = jvmtiGetCapabilities(&cap); return (error == JVMTI_ERROR_NONE && cap.can_suspend); } jvmtiError getSourceDebugExtension(jclass clazz, char **extensionPtr) { return JVMTI_FUNC_PTR(gdata->jvmti,GetSourceDebugExtension) (gdata->jvmti, clazz, extensionPtr); } /* * Convert the signature "Ljava/lang/Foo;" to a * classname "java.lang.Foo" compatible with the pattern. * Signature is overwritten in-place. */ void convertSignatureToClassname(char *convert) { char *p; p = convert + 1; while ((*p != ';') && (*p != '\0')) { char c = *p; if (c == '/') { *(p-1) = '.'; } else { *(p-1) = c; } p++; } *(p-1) = '\0'; } static void handleInterrupt(void) { /* * An interrupt is handled: * * 1) for running application threads by deferring the interrupt * until the current event handler has concluded. * * 2) for debugger threads by ignoring the interrupt; this is the * most robust solution since debugger threads don't use interrupts * to signal any condition. * * 3) for application threads that have not started or already * ended by ignoring the interrupt. In the former case, the application * is relying on timing to determine whether or not the thread sees * the interrupt; in the latter case, the interrupt is meaningless. */ jthread thread = threadControl_currentThread(); if ((thread != NULL) && (!threadControl_isDebugThread(thread))) { threadControl_setPendingInterrupt(thread); } } static jvmtiError ignore_vm_death(jvmtiError error) { if (error == JVMTI_ERROR_WRONG_PHASE) { LOG_MISC(("VM_DEAD, in debugMonitor*()?")); return JVMTI_ERROR_NONE; /* JVMTI does this, not JVMDI? */ } return error; } void debugMonitorEnter(jrawMonitorID monitor) { jvmtiError error; while (JNI_TRUE) { error = FUNC_PTR(gdata->jvmti,RawMonitorEnter) (gdata->jvmti, monitor); error = ignore_vm_death(error); if (error == JVMTI_ERROR_INTERRUPT) { handleInterrupt(); } else { break; } } if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on raw monitor enter"); } } void debugMonitorExit(jrawMonitorID monitor) { jvmtiError error; error = FUNC_PTR(gdata->jvmti,RawMonitorExit) (gdata->jvmti, monitor); error = ignore_vm_death(error); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on raw monitor exit"); } } void debugMonitorWait(jrawMonitorID monitor) { jvmtiError error; error = FUNC_PTR(gdata->jvmti,RawMonitorWait) (gdata->jvmti, monitor, ((jlong)(-1))); /* * According to the JLS (17.8), here we have * either : * a- been notified * b- gotten a suprious wakeup * c- been interrupted * If both a and c have happened, the VM must choose * which way to return - a or c. If it chooses c * then the notify is gone - either to some other * thread that is also waiting, or it is dropped * on the floor. * * a is what we expect. b won't hurt us any - * callers should be programmed to handle * spurious wakeups. In case of c, * then the interrupt has been cleared, but * we don't want to consume it. It came from * user code and is intended for user code, not us. * So, we will remember that the interrupt has * occurred and re-activate it when this thread * goes back into user code. * That being said, what do we do here? Since * we could have been notified too, here we will * just pretend that we have been. It won't hurt * anything to return in the same way as if * we were notified since callers have to be able to * handle spurious wakeups anyway. */ if (error == JVMTI_ERROR_INTERRUPT) { handleInterrupt(); error = JVMTI_ERROR_NONE; } error = ignore_vm_death(error); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on raw monitor wait"); } } void debugMonitorTimedWait(jrawMonitorID monitor, jlong millis) { jvmtiError error; error = FUNC_PTR(gdata->jvmti,RawMonitorWait) (gdata->jvmti, monitor, millis); if (error == JVMTI_ERROR_INTERRUPT) { /* See comment above */ handleInterrupt(); error = JVMTI_ERROR_NONE; } error = ignore_vm_death(error); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on raw monitor timed wait"); } } void debugMonitorNotify(jrawMonitorID monitor) { jvmtiError error; error = FUNC_PTR(gdata->jvmti,RawMonitorNotify) (gdata->jvmti, monitor); error = ignore_vm_death(error); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on raw monitor notify"); } } void debugMonitorNotifyAll(jrawMonitorID monitor) { jvmtiError error; error = FUNC_PTR(gdata->jvmti,RawMonitorNotifyAll) (gdata->jvmti, monitor); error = ignore_vm_death(error); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on raw monitor notify all"); } } jrawMonitorID debugMonitorCreate(char *name) { jrawMonitorID monitor; jvmtiError error; error = FUNC_PTR(gdata->jvmti,CreateRawMonitor) (gdata->jvmti, name, &monitor); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on creation of a raw monitor"); } return monitor; } void debugMonitorDestroy(jrawMonitorID monitor) { jvmtiError error; error = FUNC_PTR(gdata->jvmti,DestroyRawMonitor) (gdata->jvmti, monitor); error = ignore_vm_death(error); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on destruction of raw monitor"); } } /** * Return array of all threads (must be inside a WITH_LOCAL_REFS) */ jthread * allThreads(jint *count) { jthread *threads; jvmtiError error; *count = 0; threads = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetAllThreads) (gdata->jvmti, count, &threads); if (error == AGENT_ERROR_OUT_OF_MEMORY) { return NULL; /* Let caller deal with no memory? */ } if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "getting all threads"); } return threads; } /** * Fill the passed in structure with thread group info. * name field is JVMTI allocated. parent is global ref. */ void threadGroupInfo(jthreadGroup group, jvmtiThreadGroupInfo *info) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,GetThreadGroupInfo) (gdata->jvmti, group, info); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on getting thread group info"); } } /** * Return class signature string */ jvmtiError classSignature(jclass clazz, char **psignature, char **pgeneric_signature) { jvmtiError error; char *signature = NULL; /* * pgeneric_signature can be NULL, and GetClassSignature * accepts NULL. */ error = FUNC_PTR(gdata->jvmti,GetClassSignature) (gdata->jvmti, clazz, &signature, pgeneric_signature); if ( psignature != NULL ) { *psignature = signature; } else if ( signature != NULL ) { jvmtiDeallocate(signature); } return error; } /* Get class name (not signature) */ char * getClassname(jclass clazz) { char *classname; classname = NULL; if ( clazz != NULL ) { if (classSignature(clazz, &classname, NULL) != JVMTI_ERROR_NONE) { classname = NULL; } else { /* Convert in place */ convertSignatureToClassname(classname); } } return classname; /* Caller must free this memory */ } void writeGenericSignature(PacketOutputStream *out, char *genericSignature) { if (genericSignature == NULL) { (void)outStream_writeString(out, ""); } else { (void)outStream_writeString(out, genericSignature); } } jint classStatus(jclass clazz) { jint status; jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassStatus) (gdata->jvmti, clazz, &status); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on getting class status"); } return status; } static jboolean isArrayClass(jclass clazz) { jboolean isArray = JNI_FALSE; jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,IsArrayClass) (gdata->jvmti, clazz, &isArray); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on checking for an array class"); } return isArray; } static jboolean isInterface(jclass clazz) { jboolean isInterface = JNI_FALSE; jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,IsInterface) (gdata->jvmti, clazz, &isInterface); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on checking for an interface"); } return isInterface; } jvmtiError isFieldSynthetic(jclass clazz, jfieldID field, jboolean *psynthetic) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,IsFieldSynthetic) (gdata->jvmti, clazz, field, psynthetic); if ( error == JVMTI_ERROR_MUST_POSSESS_CAPABILITY ) { /* If the query is not supported, we assume it is not synthetic. */ *psynthetic = JNI_FALSE; return JVMTI_ERROR_NONE; } return error; } jvmtiError isMethodSynthetic(jmethodID method, jboolean *psynthetic) { jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,IsMethodSynthetic) (gdata->jvmti, method, psynthetic); if ( error == JVMTI_ERROR_MUST_POSSESS_CAPABILITY ) { /* If the query is not supported, we assume it is not synthetic. */ *psynthetic = JNI_FALSE; return JVMTI_ERROR_NONE; } return error; } jboolean isMethodNative(jmethodID method) { jboolean isNative = JNI_FALSE; jvmtiError error; error = JVMTI_FUNC_PTR(gdata->jvmti,IsMethodNative) (gdata->jvmti, method, &isNative); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on checking for a native interface"); } return isNative; } jboolean isSameObject(JNIEnv *env, jobject o1, jobject o2) { if ( o1==o2 ) { return JNI_TRUE; } return FUNC_PTR(env,IsSameObject)(env, o1, o2); } jint objectHashCode(jobject object) { jint hashCode = 0; jvmtiError error; if ( object!=NULL ) { error = JVMTI_FUNC_PTR(gdata->jvmti,GetObjectHashCode) (gdata->jvmti, object, &hashCode); if (error != JVMTI_ERROR_NONE) { EXIT_ERROR(error, "on getting an object hash code"); } } return hashCode; } /* Get all implemented interfaces (must be inside a WITH_LOCAL_REFS) */ jvmtiError allInterfaces(jclass clazz, jclass **ppinterfaces, jint *pcount) { jvmtiError error; *pcount = 0; *ppinterfaces = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetImplementedInterfaces) (gdata->jvmti, clazz, pcount, ppinterfaces); return error; } /* Get all loaded classes (must be inside a WITH_LOCAL_REFS) */ jvmtiError allLoadedClasses(jclass **ppclasses, jint *pcount) { jvmtiError error; *pcount = 0; *ppclasses = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetLoadedClasses) (gdata->jvmti, pcount, ppclasses); return error; } /* Get all loaded classes for a loader (must be inside a WITH_LOCAL_REFS) */ jvmtiError allClassLoaderClasses(jobject loader, jclass **ppclasses, jint *pcount) { jvmtiError error; *pcount = 0; *ppclasses = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassLoaderClasses) (gdata->jvmti, loader, pcount, ppclasses); return error; } static jboolean is_a_nested_class(char *outer_sig, int outer_sig_len, char *sig, int sep) { char *inner; /* Assumed outer class signature is "LOUTERCLASSNAME;" * inner class signature is "LOUTERCLASSNAME$INNERNAME;" * * INNERNAME can take the form: * [0-9][1-9]* anonymous class somewhere in the file * [0-9][1-9]*NAME local class somewhere in the OUTER class * NAME nested class in OUTER * * If NAME itself contains a $ (sep) then classname is further nested * inside another class. * */ /* Check prefix first */ if ( strncmp(sig, outer_sig, outer_sig_len-1) != 0 ) { return JNI_FALSE; } /* Prefix must be followed by a $ (sep) */ if ( sig[outer_sig_len-1] != sep ) { return JNI_FALSE; /* No sep follows the match, must not be nested. */ } /* Walk past any digits, if we reach the end, must be pure anonymous */ inner = sig + outer_sig_len; #if 1 /* We want to return local classes */ while ( *inner && isdigit(*inner) ) { inner++; } /* But anonymous class names can't be trusted. */ if ( *inner == ';' ) { return JNI_FALSE; /* A pure anonymous class */ } #else if ( *inner && isdigit(*inner) ) { return JNI_FALSE; /* A pure anonymous or local class */ } #endif /* Nested deeper? */ if ( strchr(inner, sep) != NULL ) { return JNI_FALSE; /* Nested deeper than we want? */ } return JNI_TRUE; } /* Get all nested classes for a class (must be inside a WITH_LOCAL_REFS) */ jvmtiError allNestedClasses(jclass parent_clazz, jclass **ppnested, jint *pcount) { jvmtiError error; jobject parent_loader; jclass *classes; char *signature; size_t len; jint count; jint ncount; int i; *ppnested = NULL; *pcount = 0; parent_loader = NULL; classes = NULL; signature = NULL; count = 0; ncount = 0; error = classLoader(parent_clazz, &parent_loader); if (error != JVMTI_ERROR_NONE) { return error; } error = classSignature(parent_clazz, &signature, NULL); if (error != JVMTI_ERROR_NONE) { return error; } len = strlen(signature); error = allClassLoaderClasses(parent_loader, &classes, &count); if ( error != JVMTI_ERROR_NONE ) { jvmtiDeallocate(signature); return error; } for (i=0; i<count; i++) { jclass clazz; char *candidate_signature; clazz = classes[i]; candidate_signature = NULL; error = classSignature(clazz, &candidate_signature, NULL); if (error != JVMTI_ERROR_NONE) { break; } if ( is_a_nested_class(signature, (int)len, candidate_signature, '$') || is_a_nested_class(signature, (int)len, candidate_signature, '#') ) { /* Float nested classes to top */ classes[i] = classes[ncount]; classes[ncount++] = clazz; } jvmtiDeallocate(candidate_signature); } jvmtiDeallocate(signature); if ( count != 0 && ncount == 0 ) { jvmtiDeallocate(classes); classes = NULL; } *ppnested = classes; *pcount = ncount; return error; } void createLocalRefSpace(JNIEnv *env, jint capacity) { /* * Save current exception since it might get overwritten by * the calls below. Note we must depend on space in the existing * frame because asking for a new frame may generate an exception. */ jobject throwable = JNI_FUNC_PTR(env,ExceptionOccurred)(env); /* * Use the current frame if necessary; otherwise create a new one */ if (JNI_FUNC_PTR(env,PushLocalFrame)(env, capacity) < 0) { EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY,"PushLocalFrame: Unable to push JNI frame"); } /* * TO DO: This could be more efficient if it used EnsureLocalCapacity, * but that would not work if two functions on the call stack * use this function. We would need to either track reserved * references on a per-thread basis or come up with a convention * that would prevent two functions from depending on this function * at the same time. */ /* * Restore exception state from before call */ if (throwable != NULL) { JNI_FUNC_PTR(env,Throw)(env, throwable); } else { JNI_FUNC_PTR(env,ExceptionClear)(env); } } jboolean isClass(jobject object) { JNIEnv *env = getEnv(); return JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->classClass); } jboolean isThread(jobject object) { JNIEnv *env = getEnv(); return JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->threadClass); } jboolean isThreadGroup(jobject object) { JNIEnv *env = getEnv(); return JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->threadGroupClass); } jboolean isString(jobject object) { JNIEnv *env = getEnv(); return JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->stringClass); } jboolean isClassLoader(jobject object) { JNIEnv *env = getEnv(); return JNI_FUNC_PTR(env,IsInstanceOf)(env, object, gdata->classLoaderClass); } jboolean isArray(jobject object) { JNIEnv *env = getEnv(); jboolean is; WITH_LOCAL_REFS(env, 1) { jclass clazz; clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, object); is = isArrayClass(clazz); } END_WITH_LOCAL_REFS(env); return is; } /** * Return property value as jstring */ static jstring getPropertyValue(JNIEnv *env, char *propertyName) { jstring valueString; jstring nameString; valueString = NULL; /* Create new String object to hold the property name */ nameString = JNI_FUNC_PTR(env,NewStringUTF)(env, propertyName); if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); /* NULL will be returned below */ } else { /* Call valueString = System.getProperty(nameString) */ valueString = JNI_FUNC_PTR(env,CallStaticObjectMethod) (env, gdata->systemClass, gdata->systemGetProperty, nameString); if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); valueString = NULL; } } return valueString; } /** * Set an agent property */ void setAgentPropertyValue(JNIEnv *env, char *propertyName, char* propertyValue) { jstring nameString; jstring valueString; if (gdata->agent_properties == NULL) { /* VMSupport doesn't exist; so ignore */ return; } /* Create jstrings for property name and value */ nameString = JNI_FUNC_PTR(env,NewStringUTF)(env, propertyName); if (nameString != NULL) { valueString = JNI_FUNC_PTR(env,NewStringUTF)(env, propertyValue); if (valueString != NULL) { /* invoke Properties.setProperty */ JNI_FUNC_PTR(env,CallObjectMethod) (env, gdata->agent_properties, gdata->setProperty, nameString, valueString); } } if (JNI_FUNC_PTR(env,ExceptionOccurred)(env)) { JNI_FUNC_PTR(env,ExceptionClear)(env); } } /** * Return property value as JDWP allocated string in UTF8 encoding */ static char * getPropertyUTF8(JNIEnv *env, char *propertyName) { jvmtiError error; char *value; value = NULL; error = JVMTI_FUNC_PTR(gdata->jvmti,GetSystemProperty) (gdata->jvmti, (const char *)propertyName, &value); if (error != JVMTI_ERROR_NONE) { jstring valueString; value = NULL; valueString = getPropertyValue(env, propertyName); if (valueString != NULL) { const char *utf; /* Get the UTF8 encoding for this property value string */ utf = JNI_FUNC_PTR(env,GetStringUTFChars)(env, valueString, NULL); /* Make a copy for returning, release the JNI copy */ value = jvmtiAllocate((int)strlen(utf) + 1); if (value != NULL) { (void)strcpy(value, utf); } JNI_FUNC_PTR(env,ReleaseStringUTFChars)(env, valueString, utf); } } if ( value == NULL ) { ERROR_MESSAGE(("JDWP Can't get property value for %s", propertyName)); EXIT_ERROR(AGENT_ERROR_NULL_POINTER,NULL); } return value; } jboolean isMethodObsolete(jmethodID method) { jvmtiError error; jboolean obsolete = JNI_TRUE; if ( method != NULL ) { error = JVMTI_FUNC_PTR(gdata->jvmti,IsMethodObsolete) (gdata->jvmti, method, &obsolete); if (error != JVMTI_ERROR_NONE) { obsolete = JNI_TRUE; } } return obsolete; } /* Get the jvmti environment to be used with tags * ANDROID_CHANGED: Expose this function publicly for use with class-track and other places. */ jvmtiEnv * getSpecialJvmti(void) { jvmtiEnv *jvmti; jvmtiError error; int rc; /* Get one time use JVMTI Env */ jvmtiCapabilities caps; // ANDROID-CHANGED: Always get a new jvmti-env using the same version as the main env. This // means that everything will still work even when using a best-effort ArtTiEnv. rc = JVM_FUNC_PTR(gdata->jvm,GetEnv) (gdata->jvm, (void **)&jvmti, jvmtiVersion()); if (rc != JNI_OK) { return NULL; } (void)memset(&caps, 0, (int)sizeof(caps)); caps.can_tag_objects = 1; error = JVMTI_FUNC_PTR(jvmti,AddCapabilities)(jvmti, &caps); if ( error != JVMTI_ERROR_NONE ) { return NULL; } return jvmti; } void writeCodeLocation(PacketOutputStream *out, jclass clazz, jmethodID method, jlocation location) { jbyte tag; if (clazz != NULL) { tag = referenceTypeTag(clazz); } else { tag = JDWP_TYPE_TAG(CLASS); } (void)outStream_writeByte(out, tag); (void)outStream_writeObjectRef(getEnv(), out, clazz); (void)outStream_writeMethodID(out, isMethodObsolete(method)?NULL:method); (void)outStream_writeLocation(out, location); } void * jvmtiAllocate(jint numBytes) { void *ptr; jvmtiError error; if ( numBytes == 0 ) { return NULL; } error = FUNC_PTR(gdata->jvmti,Allocate) (gdata->jvmti, numBytes, (unsigned char**)&ptr); if (error != JVMTI_ERROR_NONE ) { EXIT_ERROR(error, "Can't allocate jvmti memory"); } return ptr; } void jvmtiDeallocate(void *ptr) { jvmtiError error; if ( ptr == NULL ) { return; } error = FUNC_PTR(gdata->jvmti,Deallocate) (gdata->jvmti, ptr); if (error != JVMTI_ERROR_NONE ) { EXIT_ERROR(error, "Can't deallocate jvmti memory"); } } /* Rarely needed, transport library uses JDWP errors, only use? */ jvmtiError map2jvmtiError(jdwpError error) { switch ( error ) { case JDWP_ERROR(NONE): return JVMTI_ERROR_NONE; case JDWP_ERROR(INVALID_THREAD): return JVMTI_ERROR_INVALID_THREAD; case JDWP_ERROR(INVALID_THREAD_GROUP): return JVMTI_ERROR_INVALID_THREAD_GROUP; case JDWP_ERROR(INVALID_PRIORITY): return JVMTI_ERROR_INVALID_PRIORITY; case JDWP_ERROR(THREAD_NOT_SUSPENDED): return JVMTI_ERROR_THREAD_NOT_SUSPENDED; case JDWP_ERROR(THREAD_SUSPENDED): return JVMTI_ERROR_THREAD_SUSPENDED; case JDWP_ERROR(INVALID_OBJECT): return JVMTI_ERROR_INVALID_OBJECT; case JDWP_ERROR(INVALID_CLASS): return JVMTI_ERROR_INVALID_CLASS; case JDWP_ERROR(CLASS_NOT_PREPARED): return JVMTI_ERROR_CLASS_NOT_PREPARED; case JDWP_ERROR(INVALID_METHODID): return JVMTI_ERROR_INVALID_METHODID; case JDWP_ERROR(INVALID_LOCATION): return JVMTI_ERROR_INVALID_LOCATION; case JDWP_ERROR(INVALID_FIELDID): return JVMTI_ERROR_INVALID_FIELDID; case JDWP_ERROR(INVALID_FRAMEID): return AGENT_ERROR_INVALID_FRAMEID; case JDWP_ERROR(NO_MORE_FRAMES): return JVMTI_ERROR_NO_MORE_FRAMES; case JDWP_ERROR(OPAQUE_FRAME): return JVMTI_ERROR_OPAQUE_FRAME; case JDWP_ERROR(NOT_CURRENT_FRAME): return AGENT_ERROR_NOT_CURRENT_FRAME; case JDWP_ERROR(TYPE_MISMATCH): return JVMTI_ERROR_TYPE_MISMATCH; case JDWP_ERROR(INVALID_SLOT): return JVMTI_ERROR_INVALID_SLOT; case JDWP_ERROR(DUPLICATE): return JVMTI_ERROR_DUPLICATE; case JDWP_ERROR(NOT_FOUND): return JVMTI_ERROR_NOT_FOUND; case JDWP_ERROR(INVALID_MONITOR): return JVMTI_ERROR_INVALID_MONITOR; case JDWP_ERROR(NOT_MONITOR_OWNER): return JVMTI_ERROR_NOT_MONITOR_OWNER; case JDWP_ERROR(INTERRUPT): return JVMTI_ERROR_INTERRUPT; case JDWP_ERROR(INVALID_CLASS_FORMAT): return JVMTI_ERROR_INVALID_CLASS_FORMAT; case JDWP_ERROR(CIRCULAR_CLASS_DEFINITION): return JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION; case JDWP_ERROR(FAILS_VERIFICATION): return JVMTI_ERROR_FAILS_VERIFICATION; case JDWP_ERROR(ADD_METHOD_NOT_IMPLEMENTED): return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED; case JDWP_ERROR(SCHEMA_CHANGE_NOT_IMPLEMENTED): return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED; case JDWP_ERROR(INVALID_TYPESTATE): return JVMTI_ERROR_INVALID_TYPESTATE; case JDWP_ERROR(HIERARCHY_CHANGE_NOT_IMPLEMENTED): return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED; case JDWP_ERROR(DELETE_METHOD_NOT_IMPLEMENTED): return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED; case JDWP_ERROR(UNSUPPORTED_VERSION): return JVMTI_ERROR_UNSUPPORTED_VERSION; case JDWP_ERROR(NAMES_DONT_MATCH): return JVMTI_ERROR_NAMES_DONT_MATCH; case JDWP_ERROR(CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED): return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED; case JDWP_ERROR(METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED): return JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED; case JDWP_ERROR(NOT_IMPLEMENTED): return JVMTI_ERROR_NOT_AVAILABLE; case JDWP_ERROR(NULL_POINTER): return JVMTI_ERROR_NULL_POINTER; case JDWP_ERROR(ABSENT_INFORMATION): return JVMTI_ERROR_ABSENT_INFORMATION; case JDWP_ERROR(INVALID_EVENT_TYPE): return JVMTI_ERROR_INVALID_EVENT_TYPE; case JDWP_ERROR(ILLEGAL_ARGUMENT): return JVMTI_ERROR_ILLEGAL_ARGUMENT; case JDWP_ERROR(OUT_OF_MEMORY): return JVMTI_ERROR_OUT_OF_MEMORY; case JDWP_ERROR(ACCESS_DENIED): return JVMTI_ERROR_ACCESS_DENIED; case JDWP_ERROR(VM_DEAD): return JVMTI_ERROR_WRONG_PHASE; case JDWP_ERROR(UNATTACHED_THREAD): return JVMTI_ERROR_UNATTACHED_THREAD; case JDWP_ERROR(INVALID_TAG): return AGENT_ERROR_INVALID_TAG; case JDWP_ERROR(ALREADY_INVOKING): return AGENT_ERROR_ALREADY_INVOKING; case JDWP_ERROR(INVALID_INDEX): return AGENT_ERROR_INVALID_INDEX; case JDWP_ERROR(INVALID_LENGTH): return AGENT_ERROR_INVALID_LENGTH; case JDWP_ERROR(INVALID_STRING): return AGENT_ERROR_INVALID_STRING; case JDWP_ERROR(INVALID_CLASS_LOADER): return AGENT_ERROR_INVALID_CLASS_LOADER; case JDWP_ERROR(INVALID_ARRAY): return AGENT_ERROR_INVALID_ARRAY; case JDWP_ERROR(TRANSPORT_LOAD): return AGENT_ERROR_TRANSPORT_LOAD; case JDWP_ERROR(TRANSPORT_INIT): return AGENT_ERROR_TRANSPORT_INIT; case JDWP_ERROR(NATIVE_METHOD): return AGENT_ERROR_NATIVE_METHOD; case JDWP_ERROR(INVALID_COUNT): return AGENT_ERROR_INVALID_COUNT; case JDWP_ERROR(INTERNAL): return AGENT_ERROR_JDWP_INTERNAL; } return AGENT_ERROR_INTERNAL; } static jvmtiEvent index2jvmti[EI_max-EI_min+1]; static jdwpEvent index2jdwp [EI_max-EI_min+1]; void eventIndexInit(void) { (void)memset(index2jvmti, 0, (int)sizeof(index2jvmti)); (void)memset(index2jdwp, 0, (int)sizeof(index2jdwp)); index2jvmti[EI_SINGLE_STEP -EI_min] = JVMTI_EVENT_SINGLE_STEP; index2jvmti[EI_BREAKPOINT -EI_min] = JVMTI_EVENT_BREAKPOINT; index2jvmti[EI_FRAME_POP -EI_min] = JVMTI_EVENT_FRAME_POP; index2jvmti[EI_EXCEPTION -EI_min] = JVMTI_EVENT_EXCEPTION; index2jvmti[EI_THREAD_START -EI_min] = JVMTI_EVENT_THREAD_START; index2jvmti[EI_THREAD_END -EI_min] = JVMTI_EVENT_THREAD_END; index2jvmti[EI_CLASS_PREPARE -EI_min] = JVMTI_EVENT_CLASS_PREPARE; index2jvmti[EI_GC_FINISH -EI_min] = JVMTI_EVENT_GARBAGE_COLLECTION_FINISH; index2jvmti[EI_CLASS_LOAD -EI_min] = JVMTI_EVENT_CLASS_LOAD; index2jvmti[EI_FIELD_ACCESS -EI_min] = JVMTI_EVENT_FIELD_ACCESS; index2jvmti[EI_FIELD_MODIFICATION -EI_min] = JVMTI_EVENT_FIELD_MODIFICATION; index2jvmti[EI_EXCEPTION_CATCH -EI_min] = JVMTI_EVENT_EXCEPTION_CATCH; index2jvmti[EI_METHOD_ENTRY -EI_min] = JVMTI_EVENT_METHOD_ENTRY; index2jvmti[EI_METHOD_EXIT -EI_min] = JVMTI_EVENT_METHOD_EXIT; index2jvmti[EI_MONITOR_CONTENDED_ENTER -EI_min] = JVMTI_EVENT_MONITOR_CONTENDED_ENTER; index2jvmti[EI_MONITOR_CONTENDED_ENTERED -EI_min] = JVMTI_EVENT_MONITOR_CONTENDED_ENTERED; index2jvmti[EI_MONITOR_WAIT -EI_min] = JVMTI_EVENT_MONITOR_WAIT; index2jvmti[EI_MONITOR_WAITED -EI_min] = JVMTI_EVENT_MONITOR_WAITED; index2jvmti[EI_VM_INIT -EI_min] = JVMTI_EVENT_VM_INIT; index2jvmti[EI_VM_DEATH -EI_min] = JVMTI_EVENT_VM_DEATH; index2jdwp[EI_SINGLE_STEP -EI_min] = JDWP_EVENT(SINGLE_STEP); index2jdwp[EI_BREAKPOINT -EI_min] = JDWP_EVENT(BREAKPOINT); index2jdwp[EI_FRAME_POP -EI_min] = JDWP_EVENT(FRAME_POP); index2jdwp[EI_EXCEPTION -EI_min] = JDWP_EVENT(EXCEPTION); index2jdwp[EI_THREAD_START -EI_min] = JDWP_EVENT(THREAD_START); index2jdwp[EI_THREAD_END -EI_min] = JDWP_EVENT(THREAD_END); index2jdwp[EI_CLASS_PREPARE -EI_min] = JDWP_EVENT(CLASS_PREPARE); index2jdwp[EI_GC_FINISH -EI_min] = JDWP_EVENT(CLASS_UNLOAD); index2jdwp[EI_CLASS_LOAD -EI_min] = JDWP_EVENT(CLASS_LOAD); index2jdwp[EI_FIELD_ACCESS -EI_min] = JDWP_EVENT(FIELD_ACCESS); index2jdwp[EI_FIELD_MODIFICATION -EI_min] = JDWP_EVENT(FIELD_MODIFICATION); index2jdwp[EI_EXCEPTION_CATCH -EI_min] = JDWP_EVENT(EXCEPTION_CATCH); index2jdwp[EI_METHOD_ENTRY -EI_min] = JDWP_EVENT(METHOD_ENTRY); index2jdwp[EI_METHOD_EXIT -EI_min] = JDWP_EVENT(METHOD_EXIT); index2jdwp[EI_MONITOR_CONTENDED_ENTER -EI_min] = JDWP_EVENT(MONITOR_CONTENDED_ENTER); index2jdwp[EI_MONITOR_CONTENDED_ENTERED -EI_min] = JDWP_EVENT(MONITOR_CONTENDED_ENTERED); index2jdwp[EI_MONITOR_WAIT -EI_min] = JDWP_EVENT(MONITOR_WAIT); index2jdwp[EI_MONITOR_WAITED -EI_min] = JDWP_EVENT(MONITOR_WAITED); index2jdwp[EI_VM_INIT -EI_min] = JDWP_EVENT(VM_INIT); index2jdwp[EI_VM_DEATH -EI_min] = JDWP_EVENT(VM_DEATH); } jdwpEvent eventIndex2jdwp(EventIndex i) { if ( i < EI_min || i > EI_max ) { EXIT_ERROR(AGENT_ERROR_INVALID_INDEX,"bad EventIndex"); } return index2jdwp[i-EI_min]; } jvmtiEvent eventIndex2jvmti(EventIndex i) { if ( i < EI_min || i > EI_max ) { EXIT_ERROR(AGENT_ERROR_INVALID_INDEX,"bad EventIndex"); } return index2jvmti[i-EI_min]; } EventIndex jdwp2EventIndex(jdwpEvent eventType) { switch ( eventType ) { case JDWP_EVENT(SINGLE_STEP): return EI_SINGLE_STEP; case JDWP_EVENT(BREAKPOINT): return EI_BREAKPOINT; case JDWP_EVENT(FRAME_POP): return EI_FRAME_POP; case JDWP_EVENT(EXCEPTION): return EI_EXCEPTION; case JDWP_EVENT(THREAD_START): return EI_THREAD_START; case JDWP_EVENT(THREAD_END): return EI_THREAD_END; case JDWP_EVENT(CLASS_PREPARE): return EI_CLASS_PREPARE; case JDWP_EVENT(CLASS_UNLOAD): return EI_GC_FINISH; case JDWP_EVENT(CLASS_LOAD): return EI_CLASS_LOAD; case JDWP_EVENT(FIELD_ACCESS): return EI_FIELD_ACCESS; case JDWP_EVENT(FIELD_MODIFICATION): return EI_FIELD_MODIFICATION; case JDWP_EVENT(EXCEPTION_CATCH): return EI_EXCEPTION_CATCH; case JDWP_EVENT(METHOD_ENTRY): return EI_METHOD_ENTRY; case JDWP_EVENT(METHOD_EXIT): return EI_METHOD_EXIT; case JDWP_EVENT(METHOD_EXIT_WITH_RETURN_VALUE): return EI_METHOD_EXIT; case JDWP_EVENT(MONITOR_CONTENDED_ENTER): return EI_MONITOR_CONTENDED_ENTER; case JDWP_EVENT(MONITOR_CONTENDED_ENTERED): return EI_MONITOR_CONTENDED_ENTERED; case JDWP_EVENT(MONITOR_WAIT): return EI_MONITOR_WAIT; case JDWP_EVENT(MONITOR_WAITED): return EI_MONITOR_WAITED; case JDWP_EVENT(VM_INIT): return EI_VM_INIT; case JDWP_EVENT(VM_DEATH): return EI_VM_DEATH; default: break; } /* * Event type not recognized - don't exit with error as caller * may wish to return error to debugger. */ return (EventIndex)0; } EventIndex jvmti2EventIndex(jvmtiEvent kind) { switch ( kind ) { case JVMTI_EVENT_SINGLE_STEP: return EI_SINGLE_STEP; case JVMTI_EVENT_BREAKPOINT: return EI_BREAKPOINT; case JVMTI_EVENT_FRAME_POP: return EI_FRAME_POP; case JVMTI_EVENT_EXCEPTION: return EI_EXCEPTION; case JVMTI_EVENT_THREAD_START: return EI_THREAD_START; case JVMTI_EVENT_THREAD_END: return EI_THREAD_END; case JVMTI_EVENT_CLASS_PREPARE: return EI_CLASS_PREPARE; case JVMTI_EVENT_GARBAGE_COLLECTION_FINISH: return EI_GC_FINISH; case JVMTI_EVENT_CLASS_LOAD: return EI_CLASS_LOAD; case JVMTI_EVENT_FIELD_ACCESS: return EI_FIELD_ACCESS; case JVMTI_EVENT_FIELD_MODIFICATION: return EI_FIELD_MODIFICATION; case JVMTI_EVENT_EXCEPTION_CATCH: return EI_EXCEPTION_CATCH; case JVMTI_EVENT_METHOD_ENTRY: return EI_METHOD_ENTRY; case JVMTI_EVENT_METHOD_EXIT: return EI_METHOD_EXIT; /* * There is no JVMTI_EVENT_METHOD_EXIT_WITH_RETURN_VALUE. * The normal JVMTI_EVENT_METHOD_EXIT always contains the return value. */ case JVMTI_EVENT_MONITOR_CONTENDED_ENTER: return EI_MONITOR_CONTENDED_ENTER; case JVMTI_EVENT_MONITOR_CONTENDED_ENTERED: return EI_MONITOR_CONTENDED_ENTERED; case JVMTI_EVENT_MONITOR_WAIT: return EI_MONITOR_WAIT; case JVMTI_EVENT_MONITOR_WAITED: return EI_MONITOR_WAITED; case JVMTI_EVENT_VM_INIT: return EI_VM_INIT; case JVMTI_EVENT_VM_DEATH: return EI_VM_DEATH; default: EXIT_ERROR(AGENT_ERROR_INVALID_INDEX,"JVMTI to EventIndex mapping"); break; } return (EventIndex)0; } /* This routine is commonly used, maps jvmti and agent errors to the best * jdwp error code we can map to. */ jdwpError map2jdwpError(jvmtiError error) { switch ( error ) { case JVMTI_ERROR_NONE: return JDWP_ERROR(NONE); case AGENT_ERROR_INVALID_THREAD: case JVMTI_ERROR_INVALID_THREAD: return JDWP_ERROR(INVALID_THREAD); case JVMTI_ERROR_INVALID_THREAD_GROUP: return JDWP_ERROR(INVALID_THREAD_GROUP); case JVMTI_ERROR_INVALID_PRIORITY: return JDWP_ERROR(INVALID_PRIORITY); case JVMTI_ERROR_THREAD_NOT_SUSPENDED: return JDWP_ERROR(THREAD_NOT_SUSPENDED); case JVMTI_ERROR_THREAD_SUSPENDED: return JDWP_ERROR(THREAD_SUSPENDED); case JVMTI_ERROR_THREAD_NOT_ALIVE: return JDWP_ERROR(INVALID_THREAD); case AGENT_ERROR_INVALID_OBJECT: case JVMTI_ERROR_INVALID_OBJECT: return JDWP_ERROR(INVALID_OBJECT); case JVMTI_ERROR_INVALID_CLASS: return JDWP_ERROR(INVALID_CLASS); case JVMTI_ERROR_CLASS_NOT_PREPARED: return JDWP_ERROR(CLASS_NOT_PREPARED); case JVMTI_ERROR_INVALID_METHODID: return JDWP_ERROR(INVALID_METHODID); case JVMTI_ERROR_INVALID_LOCATION: return JDWP_ERROR(INVALID_LOCATION); case JVMTI_ERROR_INVALID_FIELDID: return JDWP_ERROR(INVALID_FIELDID); case AGENT_ERROR_NO_MORE_FRAMES: case JVMTI_ERROR_NO_MORE_FRAMES: return JDWP_ERROR(NO_MORE_FRAMES); case JVMTI_ERROR_OPAQUE_FRAME: return JDWP_ERROR(OPAQUE_FRAME); case JVMTI_ERROR_TYPE_MISMATCH: return JDWP_ERROR(TYPE_MISMATCH); case JVMTI_ERROR_INVALID_SLOT: return JDWP_ERROR(INVALID_SLOT); case JVMTI_ERROR_DUPLICATE: return JDWP_ERROR(DUPLICATE); case JVMTI_ERROR_NOT_FOUND: return JDWP_ERROR(NOT_FOUND); case JVMTI_ERROR_INVALID_MONITOR: return JDWP_ERROR(INVALID_MONITOR); case JVMTI_ERROR_NOT_MONITOR_OWNER: return JDWP_ERROR(NOT_MONITOR_OWNER); case JVMTI_ERROR_INTERRUPT: return JDWP_ERROR(INTERRUPT); case JVMTI_ERROR_INVALID_CLASS_FORMAT: return JDWP_ERROR(INVALID_CLASS_FORMAT); case JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION: return JDWP_ERROR(CIRCULAR_CLASS_DEFINITION); case JVMTI_ERROR_FAILS_VERIFICATION: return JDWP_ERROR(FAILS_VERIFICATION); case JVMTI_ERROR_INVALID_TYPESTATE: return JDWP_ERROR(INVALID_TYPESTATE); case JVMTI_ERROR_UNSUPPORTED_VERSION: return JDWP_ERROR(UNSUPPORTED_VERSION); case JVMTI_ERROR_NAMES_DONT_MATCH: return JDWP_ERROR(NAMES_DONT_MATCH); case AGENT_ERROR_NULL_POINTER: case JVMTI_ERROR_NULL_POINTER: return JDWP_ERROR(NULL_POINTER); case JVMTI_ERROR_ABSENT_INFORMATION: return JDWP_ERROR(ABSENT_INFORMATION); case AGENT_ERROR_INVALID_EVENT_TYPE: case JVMTI_ERROR_INVALID_EVENT_TYPE: return JDWP_ERROR(INVALID_EVENT_TYPE); case AGENT_ERROR_ILLEGAL_ARGUMENT: case JVMTI_ERROR_ILLEGAL_ARGUMENT: return JDWP_ERROR(ILLEGAL_ARGUMENT); case JVMTI_ERROR_OUT_OF_MEMORY: case AGENT_ERROR_OUT_OF_MEMORY: return JDWP_ERROR(OUT_OF_MEMORY); case JVMTI_ERROR_ACCESS_DENIED: return JDWP_ERROR(ACCESS_DENIED); case JVMTI_ERROR_WRONG_PHASE: case AGENT_ERROR_VM_DEAD: case AGENT_ERROR_NO_JNI_ENV: return JDWP_ERROR(VM_DEAD); case AGENT_ERROR_JNI_EXCEPTION: case JVMTI_ERROR_UNATTACHED_THREAD: return JDWP_ERROR(UNATTACHED_THREAD); case JVMTI_ERROR_NOT_AVAILABLE: case JVMTI_ERROR_MUST_POSSESS_CAPABILITY: return JDWP_ERROR(NOT_IMPLEMENTED); case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED: return JDWP_ERROR(HIERARCHY_CHANGE_NOT_IMPLEMENTED); case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED: return JDWP_ERROR(DELETE_METHOD_NOT_IMPLEMENTED); case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED: return JDWP_ERROR(ADD_METHOD_NOT_IMPLEMENTED); case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED: return JDWP_ERROR(SCHEMA_CHANGE_NOT_IMPLEMENTED); case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED: return JDWP_ERROR(CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED); case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED: return JDWP_ERROR(METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED); case AGENT_ERROR_NOT_CURRENT_FRAME: return JDWP_ERROR(NOT_CURRENT_FRAME); case AGENT_ERROR_INVALID_TAG: return JDWP_ERROR(INVALID_TAG); case AGENT_ERROR_ALREADY_INVOKING: return JDWP_ERROR(ALREADY_INVOKING); case AGENT_ERROR_INVALID_INDEX: return JDWP_ERROR(INVALID_INDEX); case AGENT_ERROR_INVALID_LENGTH: return JDWP_ERROR(INVALID_LENGTH); case AGENT_ERROR_INVALID_STRING: return JDWP_ERROR(INVALID_STRING); case AGENT_ERROR_INVALID_CLASS_LOADER: return JDWP_ERROR(INVALID_CLASS_LOADER); case AGENT_ERROR_INVALID_ARRAY: return JDWP_ERROR(INVALID_ARRAY); case AGENT_ERROR_TRANSPORT_LOAD: return JDWP_ERROR(TRANSPORT_LOAD); case AGENT_ERROR_TRANSPORT_INIT: return JDWP_ERROR(TRANSPORT_INIT); case AGENT_ERROR_NATIVE_METHOD: return JDWP_ERROR(NATIVE_METHOD); case AGENT_ERROR_INVALID_COUNT: return JDWP_ERROR(INVALID_COUNT); case AGENT_ERROR_INVALID_FRAMEID: return JDWP_ERROR(INVALID_FRAMEID); case JVMTI_ERROR_INTERNAL: case JVMTI_ERROR_INVALID_ENVIRONMENT: case AGENT_ERROR_INTERNAL: case AGENT_ERROR_JVMTI_INTERNAL: case AGENT_ERROR_JDWP_INTERNAL: return JDWP_ERROR(INTERNAL); default: break; } return JDWP_ERROR(INTERNAL); } jint map2jdwpSuspendStatus(jint state) { jint status = 0; if ( ( state & JVMTI_THREAD_STATE_SUSPENDED ) != 0 ) { status = JDWP_SUSPEND_STATUS(SUSPENDED); } return status; } jdwpThreadStatus map2jdwpThreadStatus(jint state) { jdwpThreadStatus status; status = (jdwpThreadStatus)(-1); if ( ! ( state & JVMTI_THREAD_STATE_ALIVE ) ) { if ( state & JVMTI_THREAD_STATE_TERMINATED ) { status = JDWP_THREAD_STATUS(ZOMBIE); } else { /* FIXUP? New JDWP #define for not started? */ status = (jdwpThreadStatus)(-1); } } else { if ( state & JVMTI_THREAD_STATE_SLEEPING ) { status = JDWP_THREAD_STATUS(SLEEPING); } else if ( state & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER ) { status = JDWP_THREAD_STATUS(MONITOR); } else if ( state & JVMTI_THREAD_STATE_WAITING ) { status = JDWP_THREAD_STATUS(WAIT); } else if ( state & JVMTI_THREAD_STATE_RUNNABLE ) { status = JDWP_THREAD_STATUS(RUNNING); } } return status; } jint map2jdwpClassStatus(jint classStatus) { jint status = 0; if ( ( classStatus & JVMTI_CLASS_STATUS_VERIFIED ) != 0 ) { status |= JDWP_CLASS_STATUS(VERIFIED); } if ( ( classStatus & JVMTI_CLASS_STATUS_PREPARED ) != 0 ) { status |= JDWP_CLASS_STATUS(PREPARED); } if ( ( classStatus & JVMTI_CLASS_STATUS_INITIALIZED ) != 0 ) { status |= JDWP_CLASS_STATUS(INITIALIZED); } if ( ( classStatus & JVMTI_CLASS_STATUS_ERROR ) != 0 ) { status |= JDWP_CLASS_STATUS(ERROR); } return status; } void log_debugee_location(const char *func, jthread thread, jmethodID method, jlocation location) { int logging_locations = LOG_TEST(JDWP_LOG_LOC); if ( logging_locations ) { char *method_name; char *class_sig; jvmtiError error; jvmtiThreadInfo info; jint state; /* Get thread information */ info.name = NULL; error = FUNC_PTR(gdata->jvmti,GetThreadInfo) (gdata->jvmti, thread, &info); if ( error != JVMTI_ERROR_NONE) { info.name = NULL; } error = FUNC_PTR(gdata->jvmti,GetThreadState) (gdata->jvmti, thread, &state); if ( error != JVMTI_ERROR_NONE) { state = 0; } /* Get method if necessary */ if ( method==NULL ) { error = FUNC_PTR(gdata->jvmti,GetFrameLocation) (gdata->jvmti, thread, 0, &method, &location); if ( error != JVMTI_ERROR_NONE ) { method = NULL; location = 0; } } /* Get method name */ method_name = NULL; if ( method != NULL ) { error = methodSignature(method, &method_name, NULL, NULL); if ( error != JVMTI_ERROR_NONE ) { method_name = NULL; } } /* Get class signature */ class_sig = NULL; if ( method != NULL ) { jclass clazz; error = methodClass(method, &clazz); if ( error == JVMTI_ERROR_NONE ) { error = classSignature(clazz, &class_sig, NULL); if ( error != JVMTI_ERROR_NONE ) { class_sig = NULL; } } } /* Issue log message */ LOG_LOC(("%s: debugee: thread=%p(%s:0x%x),method=%p(%s@%d;%s)", func, thread, info.name==NULL ? "?" : info.name, state, method, method_name==NULL ? "?" : method_name, (int)location, class_sig==NULL ? "?" : class_sig)); /* Free memory */ if ( class_sig != NULL ) { jvmtiDeallocate(class_sig); } if ( method_name != NULL ) { jvmtiDeallocate(method_name); } if ( info.name != NULL ) { jvmtiDeallocate(info.name); } } } /* ********************************************************************* */ /* JDK 6.0: Use of new Heap Iteration functions */ /* ********************************************************************* */ /* ********************************************************************* */ /* Instances */ /* Structure to hold class instances heap iteration data (arg user_data) */ typedef struct ClassInstancesData { jint instCount; jint maxInstances; jlong objTag; jvmtiError error; } ClassInstancesData; /* Callback for instance object tagging (heap_reference_callback). */ static jint JNICALL cbObjectTagInstance(jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info, jlong class_tag, jlong referrer_class_tag, jlong size, jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data) { ClassInstancesData *data; /* Check data structure */ data = (ClassInstancesData*)user_data; if (data == NULL) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } /* If we have tagged enough objects, just abort */ if ( data->maxInstances != 0 && data->instCount >= data->maxInstances ) { return JVMTI_VISIT_ABORT; } /* If tagged already, just continue */ if ( (*tag_ptr) != (jlong)0 ) { return JVMTI_VISIT_OBJECTS; } /* Tag the object so we don't count it again, and so we can retrieve it */ (*tag_ptr) = data->objTag; data->instCount++; return JVMTI_VISIT_OBJECTS; } /* Get instances for one class */ jvmtiError classInstances(jclass klass, ObjectBatch *instances, int maxInstances) { ClassInstancesData data; jvmtiHeapCallbacks heap_callbacks; jvmtiError error; jvmtiEnv *jvmti; /* Check interface assumptions */ if (klass == NULL) { return AGENT_ERROR_INVALID_OBJECT; } if ( maxInstances < 0 || instances == NULL) { return AGENT_ERROR_ILLEGAL_ARGUMENT; } /* Initialize return information */ instances->count = 0; instances->objects = NULL; /* Get jvmti environment to use */ jvmti = getSpecialJvmti(); if ( jvmti == NULL ) { return AGENT_ERROR_INTERNAL; } /* Setup data to passed around the callbacks */ data.instCount = 0; data.maxInstances = maxInstances; data.objTag = (jlong)1; data.error = JVMTI_ERROR_NONE; /* Clear out callbacks structure */ (void)memset(&heap_callbacks,0,sizeof(heap_callbacks)); /* Set the callbacks we want */ heap_callbacks.heap_reference_callback = &cbObjectTagInstance; /* Follow references, no initiating object, just this class, all objects */ error = JVMTI_FUNC_PTR(jvmti,FollowReferences) (jvmti, 0, klass, NULL, &heap_callbacks, &data); if ( error == JVMTI_ERROR_NONE ) { error = data.error; } /* Get all the instances now that they are tagged */ if ( error == JVMTI_ERROR_NONE ) { error = JVMTI_FUNC_PTR(jvmti,GetObjectsWithTags) (jvmti, 1, &(data.objTag), &(instances->count), &(instances->objects), NULL); /* Verify we got the count we expected */ if ( data.instCount != instances->count ) { error = AGENT_ERROR_INTERNAL; } } /* Dispose of any special jvmti environment */ (void)JVMTI_FUNC_PTR(jvmti,DisposeEnvironment)(jvmti); return error; } /* ********************************************************************* */ /* Instance counts. */ /* Macros to convert a class or instance tag to an index and back again */ #define INDEX2CLASSTAG(i) ((jlong)((i)+1)) #define CLASSTAG2INDEX(t) (((int)(t))-1) #define JLONG_ABS(x) (((x)<(jlong)0)?-(x):(x)) /* Structure to hold class count heap traversal data (arg user_data) */ typedef struct ClassCountData { int classCount; jlong *counts; jlong negObjTag; jvmtiError error; } ClassCountData; /* Two different cbObjectCounter's, one for FollowReferences, one for * IterateThroughHeap. Pick a card, any card. */ /* Callback for object count heap traversal (heap_reference_callback) */ static jint JNICALL cbObjectCounterFromRef(jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info, jlong class_tag, jlong referrer_class_tag, jlong size, jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data) { ClassCountData *data; int index; jlong jindex; jlong tag; /* Check data structure */ data = (ClassCountData*)user_data; if (data == NULL) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } /* Classes with no class_tag should have been filtered out. */ if ( class_tag == (jlong)0 ) { data->error = AGENT_ERROR_INTERNAL; return JVMTI_VISIT_ABORT; } /* Class tag not one we really want (jclass not in supplied list) */ if ( class_tag == data->negObjTag ) { return JVMTI_VISIT_OBJECTS; } /* If object tag is negative, just continue, we counted it */ tag = (*tag_ptr); if ( tag < (jlong)0 ) { return JVMTI_VISIT_OBJECTS; } /* Tag the object with a negative value just so we don't count it again */ if ( tag == (jlong)0 ) { /* This object had no tag value, so we give it the negObjTag value */ (*tag_ptr) = data->negObjTag; } else { /* If this object had a positive tag value, it must be one of the * jclass objects we tagged. We need to preserve the value of * this tag for later objects that might have this as a class * tag, so we just make the existing tag value negative. */ (*tag_ptr) = -tag; } /* Absolute value of class tag is an index into the counts[] array */ jindex = JLONG_ABS(class_tag); index = CLASSTAG2INDEX(jindex); if (index < 0 || index >= data->classCount) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } /* Bump instance count on this class */ data->counts[index]++; return JVMTI_VISIT_OBJECTS; } /* Callback for instance count heap traversal (heap_iteration_callback) */ static jint JNICALL cbObjectCounter(jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data) { ClassCountData *data; int index; /* Check data structure */ data = (ClassCountData*)user_data; if (data == NULL) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } /* Classes with no tag should be filtered out. */ if ( class_tag == (jlong)0 ) { data->error = AGENT_ERROR_INTERNAL; return JVMTI_VISIT_ABORT; } /* Class tag is actually an index into data arrays */ index = CLASSTAG2INDEX(class_tag); if (index < 0 || index >= data->classCount) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } /* Bump instance count on this class */ data->counts[index]++; return JVMTI_VISIT_OBJECTS; } /* Get instance counts for a set of classes */ jvmtiError classInstanceCounts(jint classCount, jclass *classes, jlong *counts) { jvmtiHeapCallbacks heap_callbacks; ClassCountData data; jvmtiError error; jvmtiEnv *jvmti; int i; /* Check interface assumptions */ if ( classes == NULL || classCount <= 0 || counts == NULL ) { return AGENT_ERROR_ILLEGAL_ARGUMENT; } /* Initialize return information */ for ( i = 0 ; i < classCount ; i++ ) { counts[i] = (jlong)0; } /* Get jvmti environment to use */ jvmti = getSpecialJvmti(); if ( jvmti == NULL ) { return AGENT_ERROR_INTERNAL; } /* Setup class data structure */ data.error = JVMTI_ERROR_NONE; data.classCount = classCount; data.counts = counts; error = JVMTI_ERROR_NONE; /* Set tags on classes, use index in classes[] as the tag value. */ error = JVMTI_ERROR_NONE; for ( i = 0 ; i < classCount ; i++ ) { if (classes[i] != NULL) { jlong tag; tag = INDEX2CLASSTAG(i); error = JVMTI_FUNC_PTR(jvmti,SetTag) (jvmti, classes[i], tag); if ( error != JVMTI_ERROR_NONE ) { break; } } } /* Traverse heap, two ways to do this for instance counts. */ if ( error == JVMTI_ERROR_NONE ) { /* Clear out callbacks structure */ (void)memset(&heap_callbacks,0,sizeof(heap_callbacks)); /* Check debug flags to see how to do this. */ if ( (gdata->debugflags & USE_ITERATE_THROUGH_HEAP) == 0 ) { /* Using FollowReferences only gives us live objects, but we * need to tag the objects to avoid counting them twice since * the callback is per reference. * The jclass objects have been tagged with their index in the * supplied list, and that tag may flip to negative if it * is also an object of interest. * All other objects being counted that weren't in the * supplied classes list will have a negative classCount * tag value. So all objects counted will have negative tags. * If the absolute tag value is an index in the supplied * list, then it's one of the supplied classes. */ data.negObjTag = -INDEX2CLASSTAG(classCount); /* Setup callbacks, only using object reference callback */ heap_callbacks.heap_reference_callback = &cbObjectCounterFromRef; /* Follow references, no initiating object, tagged classes only */ error = JVMTI_FUNC_PTR(jvmti,FollowReferences) (jvmti, JVMTI_HEAP_FILTER_CLASS_UNTAGGED, NULL, NULL, &heap_callbacks, &data); } else { /* Using IterateThroughHeap means that we will visit each object * once, so no special tag tricks here. Just simple counting. * However in this case the object might not be live, so we do * a GC beforehand to make sure we minimize this. */ /* FIXUP: Need some kind of trigger here to avoid excessive GC's? */ error = JVMTI_FUNC_PTR(jvmti,ForceGarbageCollection)(jvmti); if ( error != JVMTI_ERROR_NONE ) { /* Setup callbacks, just need object callback */ heap_callbacks.heap_iteration_callback = &cbObjectCounter; /* Iterate through entire heap, tagged classes only */ error = JVMTI_FUNC_PTR(jvmti,IterateThroughHeap) (jvmti, JVMTI_HEAP_FILTER_CLASS_UNTAGGED, NULL, &heap_callbacks, &data); } } /* Use data error if needed */ if ( error == JVMTI_ERROR_NONE ) { error = data.error; } } /* Dispose of any special jvmti environment */ (void)JVMTI_FUNC_PTR(jvmti,DisposeEnvironment)(jvmti); return error; } /* ********************************************************************* */ /* Referrers */ /* Structure to hold object referrer heap traversal data (arg user_data) */ typedef struct ReferrerData { int refCount; int maxObjects; jlong refTag; jlong objTag; jboolean selfRef; jvmtiError error; } ReferrerData; /* Callback for referrers object tagging (heap_reference_callback). */ static jint JNICALL cbObjectTagReferrer(jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info, jlong class_tag, jlong referrer_class_tag, jlong size, jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data) { ReferrerData *data; /* Check data structure */ data = (ReferrerData*)user_data; if (data == NULL) { data->error = AGENT_ERROR_ILLEGAL_ARGUMENT; return JVMTI_VISIT_ABORT; } /* If we have tagged enough objects, just abort */ if ( data->maxObjects != 0 && data->refCount >= data->maxObjects ) { return JVMTI_VISIT_ABORT; } /* If not of interest, just continue */ if ( (*tag_ptr) != data->objTag ) { return JVMTI_VISIT_OBJECTS; } /* Self reference that we haven't counted? */ if ( tag_ptr == referrer_tag_ptr ) { if ( data->selfRef == JNI_FALSE ) { data->selfRef = JNI_TRUE; data->refCount++; } return JVMTI_VISIT_OBJECTS; } /* If the referrer can be tagged, and hasn't been tagged, tag it */ if ( referrer_tag_ptr != NULL ) { if ( (*referrer_tag_ptr) == (jlong)0 ) { *referrer_tag_ptr = data->refTag; data->refCount++; } } return JVMTI_VISIT_OBJECTS; } /* Heap traversal to find referrers of an object */ jvmtiError objectReferrers(jobject obj, ObjectBatch *referrers, int maxObjects) { jvmtiHeapCallbacks heap_callbacks; ReferrerData data; jvmtiError error; jvmtiEnv *jvmti; /* Check interface assumptions */ if (obj == NULL) { return AGENT_ERROR_INVALID_OBJECT; } if (referrers == NULL || maxObjects < 0 ) { return AGENT_ERROR_ILLEGAL_ARGUMENT; } /* Initialize return information */ referrers->count = 0; referrers->objects = NULL; /* Get jvmti environment to use */ jvmti = getSpecialJvmti(); if ( jvmti == NULL ) { return AGENT_ERROR_INTERNAL; } /* Fill in the data structure passed around the callbacks */ data.refCount = 0; data.maxObjects = maxObjects; data.objTag = (jlong)1; data.refTag = (jlong)2; data.selfRef = JNI_FALSE; data.error = JVMTI_ERROR_NONE; /* Tag the object of interest */ error = JVMTI_FUNC_PTR(jvmti,SetTag) (jvmti, obj, data.objTag); /* No need to go any further if we can't tag the object */ if ( error == JVMTI_ERROR_NONE ) { /* Clear out callbacks structure */ (void)memset(&heap_callbacks,0,sizeof(heap_callbacks)); /* Setup callbacks we want */ heap_callbacks.heap_reference_callback = &cbObjectTagReferrer; /* Follow references, no initiating object, all classes, 1 tagged objs */ error = JVMTI_FUNC_PTR(jvmti,FollowReferences) (jvmti, JVMTI_HEAP_FILTER_UNTAGGED, NULL, NULL, &heap_callbacks, &data); /* Use data error if needed */ if ( error == JVMTI_ERROR_NONE ) { error = data.error; } } /* Watch out for self-reference */ if ( error == JVMTI_ERROR_NONE && data.selfRef == JNI_TRUE ) { /* Tag itself as a referer */ error = JVMTI_FUNC_PTR(jvmti,SetTag) (jvmti, obj, data.refTag); } /* Get the jobjects for the tagged referrer objects. */ if ( error == JVMTI_ERROR_NONE ) { error = JVMTI_FUNC_PTR(jvmti,GetObjectsWithTags) (jvmti, 1, &(data.refTag), &(referrers->count), &(referrers->objects), NULL); /* Verify we got the count we expected */ if ( data.refCount != referrers->count ) { error = AGENT_ERROR_INTERNAL; } } /* Dispose of any special jvmti environment */ (void)JVMTI_FUNC_PTR(jvmti,DisposeEnvironment)(jvmti); return error; }